| Introduction |
| ============ |
| The Shared Memory (SMEM) protocol allows multiple processors in Qualcomm |
| Technologies, Inc. MSM System on Chips to communicate at a low level using a |
| segment of shared system memory that is accessible by any of the processors. |
| This is accomplished by an operating-system independent mechanism that allows a |
| client on any processor to dynamically allocate a block of memory from shared |
| system memory which is then visible and accessible to clients on other |
| processors for the purpose of storing and exchanging data. |
| |
| |
| +-------------+ |
| SMEM | Processor 1 | |
| +---------+ +------------+ +-------------+ |
| | |+--------------->| Item 1 |<----------------+ ^ |
| | Linux | | | | |
| | |<---------------+| |+-------------------+ |
| +---------+ +------------+ |
| ^ + | Item 2 | +-------------+ |
| | | | |<--------------+| Processor 2 | |
| | | | |+-------------->| | |
| | | | | +-------------+ |
| | | | | |
| | | | | +-------------+ |
| | | +------------+ | Processor 3 | |
| | | . +-------------+ |
| | | . + ^ . |
| | | +------------+ | | . |
| | +----------------->| Item N |<-----------------+ | . |
| +---------------------+| |+--------------------+ . |
| | | +-------------+ |
| | |+-------------->| | |
| | | | Processor N | |
| | |<--------------+| | |
| +------------+ +-------------+ |
| |
| The SMEM driver supports all known versions of the SMEM protocol. |
| |
| Hardware description |
| ==================== |
| The SMEM protocol requires a contiguous segment of system memory that is |
| accessible by both the local processor and one or more remote processors. |
| Each processor supporting the SMEM protocol must configure their MMUs and other |
| applicable hardware such that accesses to shared memory are non-cacheable. |
| |
| Optionally, additional segments of system memory may be provided to act as |
| auxiliary memory areas for the SMEM protocol. Such segments may provide |
| performance benefits to certain processors by optimizing access latency. Such |
| auxiliary memory areas must be a slave to the single main SMEM area. |
| |
| While the SMEM protocol has provisions for software-based remote spinlocks to |
| manage synchronization between processors, this functionality may be |
| substituted with dedicated hardware. Such hardware is expected to be managed |
| by another driver providing a standardized API. |
| |
| Software description |
| ==================== |
| At its core, the SMEM protocol is a heap memory management system. The core |
| functionality consists of allocating segments of memory, and lookup operations |
| to find the address of existing segments of memory. There is no provision to |
| free memory that is allocated. |
| |
| Allocated segments of memory are called SMEM items. Each SMEM item has a unique |
| 32-bit identifier which maps each specific SMEM item to a slot in the table of |
| contents that lives at the start of the SMEM region. |
| |
| A SMEM client that wishes to allocate a SMEM item will provide the item |
| identifier and a desired size in bytes. Assuming there is enough free space in |
| the SMEM region to accommodate the request, the amount of desired bytes will be |
| carved out, and the base address and size for the item will be stored in the |
| table of contents. The base address will be returned as a pointer to the |
| client, so that the client may use the SMEM item as if it were normal memory |
| allocated through "malloc". |
| |
| A SMEM client that wishes to find an already allocated SMEM item will provide |
| the item identifier and the size in bytes that the client expects for the item. |
| A lookup in the table of contents for the specified item identifier will be |
| performed. Assuming a matching SMEM item is found, the size of the item that |
| is stored in the table of contents will be compared to the size specified by the |
| client. This sanity check of the expected vs actual size is done to ensure that |
| all users of a particular SMEM item agree on the size of the data to be |
| exchanged under the assumption that if the users do not agree on the item size, |
| then they will not be able to successfully communicate as one or more sides may |
| view a corruption of the data stored in the SMEM item. Assuming the sizes |
| match, the virtual address corresponding to the base address stored in the table |
| of contents for the item will be returned to the client. |
| |
| +------+ Request +-----------+ Memory |
| |Client|+----------------->|SMEM Driver| +---------------+ |
| +------+ Item X of size Y +-----------+ | | |
| ^ + | | |
| | | Lookup/Alloc +---------------+ Find X |
| | +--------------->| TOC[X] |+--------+ |
| | +---------------+ | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | Return pointer for client +---------------+ | |
| +---------------------------------------------+| Item X |<--------+ |
| +---------------+ |
| | | |
| | | |
| | | |
| +---------------+ |
| |
| The SMEM driver depends on the kernel memory management subsystem for managing |
| the system memory that SMEM uses. The main SMEM memory region is statically |
| mapped at boot, and the virtual address for the base of the region is stored |
| in MSM_SHARED_RAM_BASE. Auxiliary memory regions are ioremap'd at driver init. |
| All SMEM regions are mapped as non-cacheable. |
| |
| Although the SMEM driver is aware of auxiliary memory regions, and capable of |
| understanding SMEM items that exist in auxiliary memory regions, the SMEM |
| driver does not allocate from the auxiliary memory regions. A detailed |
| description of the purpose and use of auxiliary memory regions is outside the |
| scope of this document. |
| |
| Design |
| ====== |
| The SMEM protocol requires that the system bootloader initialize (zero out) and |
| bootstrap the main SMEM region before any processor in the system has booted to |
| avoid an initialization race condition. |
| |
| SMEM regions are configured as non-cachable memory. While this results in a |
| small performance hit, it significantly reduces the complexity for the SMEM |
| driver and clients in terms of cache management and memory barriers. Clients |
| are generally able to treat their SMEM items like regular local memory, which |
| eases the requirements to write correct code. |
| |
| The unsigned data type is assumed to be an unsigned 32-bit integer value. |
| |
| The root structure at the base of the main SMEM region is: |
| |
| #define SMD_HEAP_SIZE 512 |
| |
| struct smem_shared { |
| struct smem_proc_comm proc_comm[4]; |
| unsigned version[32]; |
| struct smem_heap_info heap_info; |
| struct smem_heap_entry heap_toc[SMD_HEAP_SIZE]; |
| }; |
| |
| This structure and its fields are initialized by the bootloader. |
| |
| The proc_comm field is reserved as the first part of the SMEM region to maintain |
| compatibility with legacy systems, but is otherwise deprecated. While the |
| proc comm driver is beyond the scope of this document, the remaining structure |
| definition to fully define smem_shared is: |
| |
| struct smem_proc_comm { |
| unsigned command; |
| unsigned status; |
| unsigned data1; |
| unsigned data2; |
| }; |
| |
| The version field of the smem_shared struct is an array of version entries |
| specifying the SMEM protocol version of every supporting processor active in the |
| system. Each unsigned value in the array corresponds to one entry. This |
| provides a mechanism for ensuring protocol version compatibility between |
| processors. While the full table of assigned and reserved entries in the array |
| is beyond the scope of this document, index 8 (smem_shared.version[8]) is |
| reserved for any future use by Linux. The bootloader always initializes it's |
| entry (index 7, or smem_shared.version[7]) to the SMEM protocol version |
| supported by the bootloader. Checking the value of the bootloader's entry can |
| be used as a sanity check to determine if the SMEM region was successfully |
| initialized. |
| |
| The heap_info field of smem_shared contains basic information of the SMEM heap. |
| The bootloader fills in values corresponding to the main SMEM region when it |
| initializes the heap. It is defined as: |
| |
| struct smem_heap_info { |
| unsigned initialized; |
| unsigned free_offset; |
| unsigned heap_remaining; |
| unsigned reserved; |
| }; |
| |
| The initialized field is set to 1 by the bootloader when it initializes the |
| heap. The free_offset field contains the offset from the base of the SMEM |
| region for the first free byte in the heap. When a new SMEM item is allocated, |
| free_offset is incremented by the size of the allocated item. SMEM item sizes |
| are 8-byte aligned. The heap_remaining field contains the number of free bytes |
| remaining in the heap. When a new SMEM item is allocated, heap_remaining is |
| decremented by the size of the item. The reserved field is defined to be 0. |
| |
| The heap_toc field of smem_shared is the heap table of contents. It is an array |
| containing a slot for every defined SMEM item. SMEM item identifiers index into |
| this array. The structures definition is: |
| |
| struct smem_heap_entry { |
| unsigned allocated; |
| unsigned offset; |
| unsigned size; |
| unsigned reserved; /* bits 1:0 reserved, bits 31:2 aux smem base addr */ |
| }; |
| |
| If an SMEM item is allocated, the allocated field is 1. The offset field is |
| either the offset from the main SMEM region base where this SMEM item exists, or |
| the offset from the auxiliary SMEM region base specified in the reserved field. |
| The size field contains the size of the SMEM item in bytes. The size is defined |
| to be 8-byte aligned. The reserved field is 0 if the SMEM item is located in |
| the main SMEM region, or bits 31(MSB) to 2 specify the physical address of the |
| auxiliary SMEM region where the SMEM item resides. If reserved is used as a |
| physical address, then the address must be 4-byte aligned per ARM architectural |
| requirements. |
| |
| The bootloader allocates and intializes the following SMEM items: |
| |
| Name ID Size (bytes) |
| ---------------------------------------------------- |
| SMEM_PROC_COMM 0 64 |
| SMEM_HEAP_INFO 1 16 |
| SMEM_ALLOCATION_TABLE 2 8192 |
| SMEM_VERSION_INFO 3 128 |
| SMEM_HW_RESET_DETECT 4 8 |
| SMEM_AARM_WARM_BOOT 5 4 |
| SMEM_DIAG_ERR_MESSAGE 6 200 |
| SMEM_SPINLOCK_ARRAY 7 32 |
| SMEM_MEMORY_BARRIER_LOCATION 8 4 |
| |
| All other SMEM items are dynamically allocated by processors in the system. |
| |
| Although the SMEM protocol requires the bootloader to initialize the SMEM region |
| before any processor in the system is active, early development of new systems |
| do not always have a fully functional bootloader. To determine if the |
| bootloader initialized the main SMEM region properly, the SMEM driver will check |
| the expected values of smem_shared.heap_info.initialized, |
| smem_shared.heap_info.reserved, and the bootloader entry of the |
| SMEM_VERSION_INFO SMEM item. If this check fails, the SMEM driver will print |
| an error message to the kernel log, and enter a disabled state. |
| |
| Security Feature |
| ---------------- |
| The SMEM protocol supports an optional security feature that segments the main |
| SMEM region into multiple partitions. Each partition becomes a unique item |
| namespace. Access to partitions is restricted to a maximum of two processors |
| and enforced by Memory Protection Units (MPUs). The exceptions to this are the |
| Partition Table of Contents partition, which is read-only accessible by all |
| processors, and the Legacy/Default partition, which is freely accessible by all |
| processors. |
| |
| +-------------------------+ SMEM Base address |
| |Legacy/Default | |
| |SMEM Partition | |
| +-------------------------+ |
| |SMEM Partition 0 | |
| |Processor 1 - Processor 2| |
| +-------------------------+ |
| |SMEM Partition 1 | |
| |Processor 1 - Processor 3| |
| +-------------------------+ |
| |SMEM Partition 2 | |
| |Processor 4 - Processor 5| |
| +-------------------------+ |
| . |
| . |
| . |
| +-------------------------+ |
| |SMEM Partition N | |
| |Processor N - Processor M| |
| +-------------------------+ SMEM Base address + SMEM size - 4k |
| |Table of Contents | |
| | | |
| +-------------------------+ SMEM Base address + SMEM size |
| |
| SMEM items which are point-to-point in nature and accessed by two or fewer |
| processors may be allocated from a partition that is restricted to those |
| processors. SMEM items which are non-sensitive, accessed by 3 or more |
| processors, and/or do not correspond to a secured partition are allocated from |
| the Legacy/Default partition. |
| |
| During the firmware boot process, the Table of Contents is initialized with a |
| description of all the secured partitions. Each secured partition is also |
| initialized. The required MPU settings to protect the Table of Contents and the |
| secured partitions are also established. The Table of Contents is located 4k |
| bytes prior to the end of the main SMEM region so that it is in a known position |
| for all processors to find and do local configuration. |
| |
| The Table of Contents is defined as: |
| |
| struct smem_toc { |
| /* |
| * Identifier is a constant for use in debugging and identifying this |
| * struct in a binary capture. Set to 0x434f5424 ("$TOC"). |
| */ |
| uint32_t identifier; |
| |
| /* Version number */ |
| uint32_t version; |
| |
| /* Number of entries in the table */ |
| uint32_t num_entries; |
| |
| uint32_t reserved[5]; |
| |
| /* Zero or more entries follow */ |
| struct smem_toc_entry entry[]; |
| }; |
| |
| Each entry in the Table of Contents is defined as: |
| |
| struct smem_toc_entry { |
| /* Offset in bytes from SMEM base of the region */ |
| uint32_t offset; |
| |
| /* Size in bytes of the region */ |
| uint32_t size; |
| |
| /* Flags for this region */ |
| uint32_t flags; |
| |
| /* |
| * IDs for the 2 subsystems which have access to this partition. |
| * Order does not matter. |
| * For the entry which describes the TOC itself, these are both set to |
| * SMEM_INVALID_HOST. |
| * Use uint16_t, rather than enum type, to ensure size. |
| */ |
| uint16_t host0; |
| uint16_t host1; |
| |
| /* |
| * Lowest common multiple of cacheline sizes for both endpoints. For |
| * example, if host0 has cacheline size of 32 and host1 has cacheline |
| * size of 64, this value is set to 64. |
| */ |
| uint32_t size_cacheline; |
| |
| uint32_t reserved[3]; |
| |
| /* |
| * Sizes of sub ranges that are part of the region, but are excluded |
| * from the SMEM heap. These are allocated from the end of the region |
| * starting with sizes[0]. Set to 0 when not used. |
| */ |
| uint32_t exclusion_sizes[SMEM_TOC_MAX_EXCLUSIONS]; |
| }; |
| |
| While the Legacy/Default partition maintains the structure and format of the |
| main SMEM region with the security feature disabled, the secured partitions have |
| a different format and structure: |
| |
| +--------------+ +--------------------------+ Partition Base Address |
| | | | Partition Header | |
| | | | | + |
| | Uncached Page| +--------------------------+ | |
| | | | Item A Header | | |
| | | +--------------------------+ | |
| | | | Item A Data | | |
| +--------------+ | | | |
| | | | | | |
| | | +--------------------------+ | |
| | Uncached Page| | Item B Header | |Direction of heap growth |
| | | +--------------------------+ | |
| | | | Item B Data | | |
| | | | | | |
| +--------------+ | | | |
| | | +--------------------------+ | |
| | | | | | |
| | Uncached Page| | Unused Heap | | |
| | | | space to | v |
| | | | page boundary | |
| | | | | |
| +--------------+ +--------------------------+<----------+ End of heap |
| . . . . . . |
| Free Space |
| Can be used for |
| either heap. |
| . . . . . . |
| +--------------+ +--------------------------+<----------+ End of heap |
| | | | | |
| | | | Unused Heap | |
| | Cached Page | | space to | ^ |
| | | | page boundary | | |
| | | +--------------------------+ | |
| | | | Item Y Data | | |
| +--------------+ | | | |
| | | +--------------------------+ | |
| | | | Item Y Header | | |
| | Cached Page | +--------------------------+ | |
| | | | Item Y Header Padding | |Direction of heap growth |
| | | +--------------------------+ | |
| | | | Item Z Data | | |
| +--------------+ | | | |
| | | | | | |
| | | | | | |
| | Cached Page | +--------------------------+ | |
| | | | Item Z Header | | |
| | | +--------------------------+ + Padding is here to ensure |
| | | | Item Z Header Padding | the the data buffer start |
| +--------------+ +--------------------------+ and end addresses are |
| | | aligned to cachelines for |
| | | Exclusion Range both endpoints. |
| | Uncached Page|. . . Free Space . . . |
| | | +--------------------------+ |
| | | | Exclusion Ranges 0..N | |
| | | | | |
| +--------------+ +--------------------------+ Partition Base Address + size |
| |
| The design of the secured partitions has two advantages over the Legacy/Default |
| Partition |
| 1. Using a linked list instead of a static array to track allocated SMEM |
| items maximizes space utilization |
| 2. Creating two heaps allows one to be cacheline aligned, thus providing |
| an option for a higher level of performance to clients (requires |
| client to specify they want their SMEM item allocated in the |
| cached area) |
| |
| The partition header struct is defined as: |
| |
| struct smem_partition_header { |
| /* Identifier magic number - 0x54525024 ("$PRT") */ |
| uint32_t identifier; |
| |
| /* |
| * IDs for the 2 subsystems which have access to this partition. |
| * Order does not matter. |
| * Use uint16_t, rather than enum type, to ensure size. |
| */ |
| uint16_t host0; |
| uint16_t host1; |
| |
| /* Partition size, in bytes, not including the exclusion ranges */ |
| uint32_t size; |
| |
| /* Offset of the byte following the last allocation in uncached heap */ |
| uint32_t offset_free_uncached; |
| |
| /* Offset of the byte following the last allocation in cached heap */ |
| uint32_t offset_free_cached; |
| |
| uint32_t reserved[3]; |
| }; |
| |
| The allocated SMEM item header struct is defined as: |
| |
| struct smem_partition_allocation_header { |
| /* 0xa5a5 canary value to detect overrun problems */ |
| uint16_t canary; |
| |
| /* SMEM item ID. Use uint16_t here, rather than enum, to ensure size. */ |
| uint16_t smem_id; |
| |
| /* Size of the allocated item, includes any necessary padding. */ |
| uint32_t size; |
| |
| /* Size of the data padding for cacheline alignment, if applicable */ |
| uint16_t data_padding; |
| |
| /* Size of the header padding for cacheline alignment, if applicable */ |
| uint16_t header_padding; |
| |
| uint32_t reserved[1]; |
| }; |
| |
| SMP/multi-core |
| ============== |
| The SMEM driver expects a remote spinlock driver to provide inter-processor |
| synchronization primitives which not only provide locking between multiple cores |
| but locking between multiple processors to protect the state of structures |
| stored in SMEM regions during allocation and lookup. Once a pointer to a SMEM |
| item is returned to a client, that client is expected to provide all the |
| necessary locking and other synchronization as required. |
| |
| The remote spinlocks may make use of the SMEM_SPINLOCK_ARRAY SMEM item (typical |
| of legacy systems). |
| |
| SMEM regions are non-cachable to maintain a consistent state of the data |
| throughout all operations. This simplifies cache management and memory barrier |
| requirements to a few key points in the SMEM item allocation process, and allows |
| clients to treat SMEM items like local memory once allocated. |
| |
| Security |
| ======== |
| SMEM by default provides no security of SMEM items. If a SMEM item is intended |
| to only be used between clients on processors A and B, malicious clients on |
| processor C are free to sniff or inject data into the SMEM item. |
| |
| An optional security feature may be enabled that makes use of Memory Protection |
| Units (MPUs) to limit access of special segments of the main SMEM region. |
| Access to these partitions is limited to two processors, so only point-to-point |
| traffic (such as SMD or SMP2P) is able to be protected. Auxiliary SMEM regions |
| are not protected under this feature. Support for this feature is activated by |
| a Device Tree property. |
| |
| Performance |
| =========== |
| Some client use cases such as SMD may benefit from caching, but that places an |
| additional burden of cache maintenance and protocol design onto the clients. |
| |
| Interface |
| ========= |
| Kernel-space APIs: |
| |
| /** |
| * smem_alloc() - Find an existing item, otherwise allocate it with security |
| * support |
| * |
| * @id: ID of SMEM item |
| * @size_in: Size of the SMEM item |
| * @to_proc: SMEM host that shares the item with apps |
| * @flags: Item attribute flags |
| * @returns: Pointer to SMEM item, NULL if it couldn't be found/allocated, or |
| * -EPROBE_DEFER if the driver is not ready |
| */ |
| void *smem_alloc(unsigned id, unsigned size_in, unsigned to_proc, |
| unsigned flags); |
| |
| /** |
| * smem_get_entry() - Get existing item with security support |
| * |
| * @id: ID of SMEM item |
| * @size: Pointer to size variable for storing the result |
| * @to_proc: SMEM host that shares the item with apps |
| * @flags: Item attribute flags |
| * @returns: Pointer to SMEM item, NULL if it doesn't exist, or -EPROBE_DEFER |
| * if the driver isn't ready |
| */ |
| void *smem_get_entry(unsigned id, unsigned *size, unsigned to_proc, |
| unsigned flags); |
| |
| /** |
| * smem_get_entry_no_rlock() - Get existing item without using remote spinlock. |
| * |
| * @id: ID of SMEM item |
| * @size_out: Pointer to size variable for storing the result |
| * @to_proc: SMEM host that shares the item with apps |
| * @flags: Item attribute flags |
| * @returns: Pointer to SMEM item, NULL if it doesn't exist, or -EPROBE_DEFER |
| * if the driver isn't ready |
| * |
| * This function does not lock the remote spinlock and should only be used in |
| * failure-recover cases such as retrieving the subsystem failure reason during |
| * subsystem restart. |
| */ |
| void *smem_get_entry_no_rlock(unsigned id, unsigned *size_out, unsigned to_proc, |
| unsigned flags); |
| |
| /** |
| * smem_find() - Find existing item with security support |
| * |
| * @id: ID of SMEM item |
| * @size_in: Size of the SMEM item |
| * @to_proc: SMEM host that shares the item with apps |
| * @flags: Item attribute flags |
| * @returns: Pointer to SMEM item, NULL if it doesn't exist, or -EPROBE_DEFER |
| * if the driver is not ready |
| */ |
| void *smem_find(unsigned id, unsigned size); |
| |
| /** |
| * smem_virt_to_phys() - Convert SMEM address to physical address. |
| * |
| * @smem_address: Address of SMEM item (returned by smem_alloc(), etc) |
| * @returns: Physical address (or NULL if there is a failure) |
| * |
| * This function should only be used if an SMEM item needs to be handed |
| * off to a DMA engine. This function will not return a version of EPROBE_DEFER |
| * if the driver is not ready since the caller should obtain @smem_address from |
| * one of the other public APIs and get EPROBE_DEFER at that time, if |
| * applicable. |
| */ |
| phys_addr_t smem_virt_to_phys(void *smem_address); |
| |
| Driver parameters |
| ================= |
| Module parameters: |
| debug_mask - 0 for off (default), 1 for on. |
| Enables or disables printing debug messages to the kernel log |
| |
| Config options |
| ============== |
| Configuration of SMEM regions is done via Device Tree per the format in |
| Documentation/devicetree/bindings/arm/msm/smem.txt. |
| |
| Dependencies |
| ============ |
| Drivers needed: |
| Remote spinlocks |
| |
| Depends on the system bootloader to initialize the main SMEM region. |
| |
| Known issues |
| ============ |
| None. |
| |
| To do |
| ===== |
| Convert use of the unsigned data type to well defined value such as uint32_t for |
| better portability. |