summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorYang Liu <yliu21@qti.qualcomm.com>2026-06-09 12:13:52 +0200
committerNaresh Nunna <nnunna@qti.qualcomm.com>2026-07-14 13:58:07 +0530
commit8812515ff25ededfd6de0d07ceb89a1f857cd7b9 (patch)
treec6317222653d4234b414e132898f9f56934292f2 /drivers
parentb58c2502dabbb431fa8340404db26b1355505a6a (diff)
downloadarm-trusted-firmware-8812515ff25ededfd6de0d07ceb89a1f857cd7b9.tar.gz
arm-trusted-firmware-8812515ff25ededfd6de0d07ceb89a1f857cd7b9.zip
feat(qti): add SMEM driver
SMEM (Shared Memory) provides a physically shared memory region used for inter-processor communication across multiple subsystems. This change introduces an SMEM driver in TF-A that acts purely as a consumer of SMEM information initialized by the boot firmware. The driver does not perform SMEM initialization or partition management. The SMEM data is consumed by other TF-A platform drivers, including: - chipinfo - platforminfo - power - clock Change-Id: I48eec305b35af92dc6e9625fc3b72565c8c8e719 Signed-off-by: Yang Liu <yliu21@qti.qualcomm.com> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com> Signed-off-by: Naresh Nunna <nnunna@qti.qualcomm.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/qti/smem/smem.c192
-rw-r--r--drivers/qti/smem/smem.mk13
-rw-r--r--drivers/qti/smem/smem_partition.c376
3 files changed, 581 insertions, 0 deletions
diff --git a/drivers/qti/smem/smem.c b/drivers/qti/smem/smem.c
new file mode 100644
index 000000000..4e43c290c
--- /dev/null
+++ b/drivers/qti/smem/smem.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <errno.h>
+
+#include <common/debug.h>
+#include <lib/mmio.h>
+#include <plat/common/platform.h>
+
+#include "smem/smem.h"
+#include "smem/smem_internal.h"
+#include "smem/smem_partition.h"
+#include "smem/smem_version.h"
+
+#define SMEM_LEGACY_VERSION_ID 0x000B0000
+
+struct smem_info smem_info;
+
+static int32_t smem_init_check(struct smem_info *sinfo, enum smem_mem_type smem_type)
+{
+ if (sinfo->state != SMEM_STATE_INITIALIZED) {
+ smem_init();
+ }
+
+ if ((sinfo->state != SMEM_STATE_INITIALIZED) ||
+ (sinfo->funcs == NULL)) {
+ ERROR("SMEM: not initialized state=%d! smem_type=%d\n",
+ sinfo->state, smem_type);
+ return SMEM_STATUS_FAILURE;
+ }
+
+ if (smem_type >= sinfo->max_items) {
+ ERROR("SMEM: trying to allocate/get smem item=%d which is >= max item=%d\n",
+ smem_type, sinfo->max_items);
+ return SMEM_STATUS_INVALID_PARAM;
+ }
+
+ return SMEM_STATUS_SUCCESS;
+}
+
+void *smem_alloc(enum smem_mem_type smem_type, uint32_t buf_size)
+{
+ void *smem_buf;
+ uint32_t ret_size;
+
+ smem_buf = smem_get_addr(smem_type, &ret_size);
+ if (smem_buf == NULL) {
+ ERROR("SMEM: %s is not supported only get address is "
+ "supported. smem_type=%d was not allocated already\n",
+ __func__, smem_type);
+ } else if (ret_size != buf_size) {
+ VERBOSE("SMEM: %s smem_type=%d was allocated with different "
+ "size=%d already, expected=%d\n",
+ __func__, smem_type, ret_size, buf_size);
+
+ smem_buf = NULL;
+ }
+
+ return smem_buf;
+}
+
+void *smem_get_addr(enum smem_mem_type smem_type, uint32_t *buf_size)
+{
+ struct smem_info *sinfo = &smem_info;
+ void *smem_buf;
+
+ if (smem_init_check(sinfo, smem_type) != SMEM_STATUS_SUCCESS) {
+ return NULL;
+ }
+ if (!sinfo->funcs->get_addr) {
+ return NULL;
+ }
+
+ smem_buf = sinfo->funcs->get_addr(smem_type, buf_size);
+
+ if (smem_buf == NULL) {
+ ERROR("SMEM: get addr failed! smem_type=%d\n",
+ smem_type);
+ } else {
+ VERBOSE("SMEM: get addr success. smem_type=%d, buf_size=%d and offset=0x%08lX\n",
+ smem_type, buf_size ? *buf_size : 0xFFFFFFFF,
+ (unsigned long)((uint8_t *)smem_buf - sinfo->smem_base_addr));
+ }
+ return smem_buf;
+}
+
+bool smem_version_set(enum smem_mem_type type, uint32_t version, uint32_t mask)
+{
+ uint32_t idx;
+ uint32_t my_version;
+ uint32_t other_version;
+ bool match = true;
+ uint32_t *version_array;
+
+ if ((type != SMEM_VERSION_INFO) &&
+ ((type < SMEM_VERSION_FIRST) || (type > SMEM_VERSION_LAST))) {
+ return false;
+ }
+
+ if (type == SMEM_VERSION_INFO) {
+ struct smem_static_allocs *static_allocs =
+ (struct smem_static_allocs *)smem_info.smem_base_addr;
+
+ version_array = static_allocs->version;
+ } else {
+ version_array = (uint32_t *)smem_alloc(
+ type, SMEM_VERSION_INFO_SIZE * sizeof(uint32_t));
+ if (version_array == NULL) {
+ ERROR("SMEM: %s: unable to allocate version array: type %d\n",
+ __func__, type);
+ plat_error_handler(EFAULT);
+ return false;
+ }
+ }
+
+ my_version = (version & mask);
+ version_array[smem_info.version_offset] |= my_version;
+
+ for (idx = 0; idx < SMEM_VERSION_INFO_SIZE; idx++) {
+ other_version = version_array[idx] & mask;
+
+ if ((other_version != 0) && (other_version != my_version)) {
+ match = false;
+ idx = SMEM_VERSION_INFO_SIZE;
+ }
+ }
+
+ return match;
+}
+
+static uint32_t smem_boot_version_get(struct smem_info *sinfo)
+{
+ struct smem_static_allocs *static_allocs =
+ (struct smem_static_allocs *)sinfo->smem_base_addr;
+
+ return mmio_read_32((uintptr_t)&static_allocs->version[SMEM_VERSION_TZ_HYP_OFFSET]);
+}
+
+void smem_init(void)
+{
+ struct smem_info *sinfo = &smem_info;
+
+ uint32_t version;
+
+ if (sinfo->state == SMEM_STATE_INITIALIZED) {
+ return;
+ }
+
+ sinfo->this_host = SMEM_TZ;
+
+ /*
+ * Bound for the item-type range check in smem_init_check(). Valid types
+ * are SMEM_MEM_FIRST..SMEM_MEM_LAST; SMEM_INVALID is one past the last.
+ * Without this the zero-initialised max_items rejects every lookup.
+ */
+ sinfo->max_items = SMEM_INVALID;
+
+ /*
+ * SMEM is statically mapped as part of QTI_DEVICE region.
+ * Since it's 1:1 mapping, use physical address directly as virtual address.
+ */
+ sinfo->smem_base_addr = (uint8_t *)QTI_SMEM_BASE;
+ sinfo->smem_size = QTI_SMEM_SIZE;
+ version = smem_boot_version_get(sinfo);
+
+ if ((version & SMEM_MAJOR_VERSION_MASK) == SMEM_LEGACY_VERSION_ID) {
+ ERROR("SMEM: version 0x%X is not supported\n", version);
+ plat_error_handler(EFAULT);
+ } else {
+ sinfo->version = SMEM_VERSION_ID;
+
+ sinfo->funcs = &smem_part_funcs;
+ }
+
+ smem_part_init();
+
+ /*
+ * Moved before the version check/set in order to fulfill that function's
+ * dependency that smem has been initialized (and indicate so).
+ */
+ sinfo->state = SMEM_STATE_INITIALIZED;
+
+ if (smem_version_set(SMEM_VERSION_INFO, sinfo->version,
+ SMEM_MAJOR_VERSION_MASK) == false) {
+ ERROR("SMEM: %s: major version (%d) does not match all procs\n",
+ __func__, sinfo->version);
+ plat_error_handler(EFAULT);
+ return;
+ }
+}
diff --git a/drivers/qti/smem/smem.mk b/drivers/qti/smem/smem.mk
new file mode 100644
index 000000000..d1ae80ed2
--- /dev/null
+++ b/drivers/qti/smem/smem.mk
@@ -0,0 +1,13 @@
+#
+# Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+PLAT_DRIVERS_PATH := drivers/qti
+
+PLAT_INCLUDES += -Iinclude/drivers/qti \
+ -Iinclude/drivers/qti/smem
+
+BL31_SOURCES += $(PLAT_DRIVERS_PATH)/smem/smem.c \
+ $(PLAT_DRIVERS_PATH)/smem/smem_partition.c
diff --git a/drivers/qti/smem/smem_partition.c b/drivers/qti/smem/smem_partition.c
new file mode 100644
index 000000000..4f85eeedf
--- /dev/null
+++ b/drivers/qti/smem/smem_partition.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <errno.h>
+
+#include <common/debug.h>
+#include <lib/mmio.h>
+#include <lib/utils_def.h>
+#include <plat/common/platform.h>
+
+#include "smem/smem.h"
+#include "smem/smem_internal.h"
+#include "smem/smem_partition.h"
+#include "smem/smem_toc.h"
+#include "smem/smem_version.h"
+
+/* Mask for smallest possible page mapping size */
+#define SMEM_PAGE_ALIGN_MASK 0x00000FFF
+
+#define SMEM_PART_INVALID_HOST_INDX 0xFFFFFFFF
+
+const struct smem_funcs smem_part_funcs = {
+ smem_part_get_addr,
+};
+
+static struct smem_partition_info *smem_info_prt;
+static uint32_t smem_info_num_prt;
+
+static int32_t smem_part_process_item(
+ struct smem_partition_info *info, struct smem_alloc_params *params,
+ struct smem_partition_allocation_header **item_hdr, uint16_t padding_header,
+ uint32_t *size_remaining, bool cached)
+{
+ uint32_t size_item;
+ uint16_t smem_type;
+ uint16_t canary;
+ uint32_t size_total;
+ uint32_t padding_data;
+ bool found = false;
+
+ canary = mmio_read_16((uintptr_t)&((*item_hdr)->canary));
+ smem_type = mmio_read_16((uintptr_t)&((*item_hdr)->smem_type));
+ size_item = mmio_read_32((uintptr_t)&((*item_hdr)->size));
+
+ size_total = size_item + padding_header +
+ sizeof(struct smem_partition_allocation_header);
+
+ if (canary != SMEM_ALLOC_HDR_CANARY) {
+ ERROR("SMEM: invalid canary 0x%X at location 0x%X\n",
+ canary, (uint32_t)(uintptr_t)(*item_hdr));
+ return SMEM_STATUS_FAILURE;
+ }
+
+ if (size_item > info->size) {
+ ERROR("SMEM: invalid item size:%d, partition size:%d smem_type:0x%04X\n",
+ size_item, info->size, smem_type);
+ return SMEM_STATUS_FAILURE;
+ }
+
+ if (size_total > *size_remaining) {
+ ERROR("SMEM: invalid item size. Total size: %d, remaining: %d\n",
+ size_total, *size_remaining);
+ return SMEM_STATUS_FAILURE;
+ }
+
+ *size_remaining -= size_total;
+
+ do {
+ if (smem_type != params->smem_type) {
+ break;
+ }
+
+ if (cached) {
+ params->buffer =
+ (void *)((size_t)(*item_hdr) - size_item);
+ params->flags |= SMEM_ALLOC_FLAG_CACHED;
+ } else {
+ params->buffer =
+ (void *)((size_t)(*item_hdr) +
+ sizeof(struct smem_partition_allocation_header));
+ params->flags &= ~SMEM_ALLOC_FLAG_CACHED;
+ }
+
+ padding_data = mmio_read_16((uintptr_t)&(*item_hdr)->padding_data);
+ if (padding_data > size_item) {
+ ERROR("SMEM: invalid padding data size:%d, size_item:%d smem_type:0x%04X\n",
+ padding_data, size_item, smem_type);
+ return SMEM_STATUS_FAILURE;
+ }
+
+ params->size = size_item - padding_data;
+
+ params->size = ROUND_UP(params->size, 8);
+
+ found = true;
+ } while (0);
+
+ if (cached) {
+ *item_hdr = (struct smem_partition_allocation_header
+ *)((size_t)(*item_hdr) - size_total);
+ } else {
+ *item_hdr = (struct smem_partition_allocation_header
+ *)((size_t)(*item_hdr) + size_total);
+ }
+
+ return found ? SMEM_STATUS_SUCCESS : SMEM_STATUS_NOT_FOUND;
+}
+
+static bool smem_part_is_host_present(struct smem_toc_entry *toc_entry)
+{
+ uint16_t host0 = mmio_read_16((uintptr_t)&toc_entry->host0);
+ uint16_t host1 = mmio_read_16((uintptr_t)&toc_entry->host1);
+ uint32_t size = mmio_read_32((uintptr_t)&toc_entry->size);
+
+ if (!size) {
+ return false;
+ }
+
+ /* Check for common partition, host ids should be 0xFFFE */
+ if (host0 == SMEM_COMMON_HOST && host1 == SMEM_COMMON_HOST) {
+ return true;
+ }
+
+ return false;
+}
+
+static struct smem_partition_info *smem_part_info_alloc(uint32_t *part_info_cnt)
+{
+ static struct smem_partition_info smem_info_prt_static = { 0 };
+
+ *part_info_cnt = 1; /* Only common partition allocation supported */
+
+ return &smem_info_prt_static;
+}
+
+static struct smem_partition_info *smem_part_info_get(uint16_t remote_host)
+{
+ uint32_t indx;
+
+ if ((remote_host == SMEM_INVALID_HOST) ||
+ (remote_host == smem_info.this_host)) {
+ remote_host = SMEM_COMMON_HOST;
+ }
+
+ for (indx = 0; indx < smem_info_num_prt; indx++) {
+ struct smem_partition_info *info = &smem_info_prt[indx];
+
+ if (info->host0 == remote_host || info->host1 == remote_host) {
+ return info;
+ }
+ }
+
+ return NULL;
+}
+
+
+int32_t smem_part_get_addr_ex(struct smem_alloc_params *params)
+{
+ struct smem_partition_info *info;
+ struct smem_partition_allocation_header *item_hdr;
+ uint32_t size_remaining;
+ uint8_t *limit_addr;
+ uint16_t padding_header;
+ int32_t ret;
+
+ info = smem_part_info_get(params->remote_host);
+ if (!info) {
+ params->buffer = NULL;
+ return SMEM_STATUS_NOT_FOUND;
+ }
+
+ info->offset_free_cached =
+ mmio_read_32((uintptr_t)&info->header->offset_free_cached);
+ info->offset_free_uncached =
+ mmio_read_32((uintptr_t)&info->header->offset_free_uncached);
+
+ if ((info->offset_free_uncached > info->offset_free_cached) ||
+ (info->offset_free_uncached < sizeof(struct smem_partition_header)) ||
+ (info->offset_free_uncached > info->size) ||
+ (info->offset_free_cached > info->size)) {
+ ERROR("SMEM: invalid heap pointers. Uncached: 0x%X, cached: 0x%X\n",
+ info->offset_free_uncached, info->offset_free_cached);
+ return SMEM_STATUS_OUT_OF_RESOURCES;
+ }
+
+ size_remaining = info->size - sizeof(struct smem_partition_header);
+
+ padding_header = 0;
+
+ item_hdr = (struct smem_partition_allocation_header
+ *)((uint8_t *)info->header +
+ sizeof(struct smem_partition_header));
+
+ limit_addr = (uint8_t *)info->header + info->offset_free_uncached;
+
+ while ((uint8_t *)item_hdr +
+ sizeof(struct smem_partition_allocation_header) <=
+ limit_addr) {
+ ret = smem_part_process_item(
+ info,
+ params,
+ &item_hdr,
+ padding_header,
+ &size_remaining,
+ false);
+
+ if (ret == SMEM_STATUS_SUCCESS || ret == SMEM_STATUS_FAILURE) {
+ return ret;
+ }
+ }
+
+ padding_header = SMEM_PARTITION_ITEM_PADDING(info->size_cacheline);
+
+ item_hdr = (struct smem_partition_allocation_header
+ *)((uint8_t *)info->header + info->size -
+ padding_header -
+ sizeof(struct smem_partition_allocation_header));
+
+ limit_addr = (uint8_t *)info->header + info->offset_free_cached;
+
+ while (((uint8_t *)item_hdr >= limit_addr) &&
+ (((uint8_t *)item_hdr +
+ sizeof(struct smem_partition_allocation_header)) <=
+ ((uint8_t *)info->header + info->size))) {
+ ret = smem_part_process_item(
+ info,
+ params,
+ &item_hdr,
+ padding_header,
+ &size_remaining,
+ true);
+
+ if (ret == SMEM_STATUS_SUCCESS || ret == SMEM_STATUS_FAILURE) {
+ return ret;
+ }
+ }
+
+ return SMEM_STATUS_NOT_FOUND;
+}
+
+
+void *smem_part_get_addr(enum smem_mem_type smem_type, uint32_t *buf_size)
+{
+ struct smem_alloc_params params;
+ int32_t ret;
+
+ params.remote_host = SMEM_INVALID_HOST;
+ params.smem_type = smem_type;
+ params.size = 0;
+ params.buffer = NULL;
+ params.flags = SMEM_ALLOC_FLAG_NONE;
+
+ ret = smem_part_get_addr_ex(&params);
+ *buf_size = params.size;
+ return (ret == SMEM_STATUS_SUCCESS) ? params.buffer : NULL;
+}
+
+void smem_part_init(void)
+{
+ struct smem_toc *toc;
+ uint8_t *smem_base;
+ uint32_t smem_size;
+ uint32_t num_entries;
+ uint16_t this_host;
+
+ uint32_t i, part_indx;
+ uint32_t size;
+ int32_t status;
+
+ smem_base = smem_info.smem_base_addr;
+ smem_size = smem_info.smem_size;
+ this_host = smem_info.this_host;
+
+ toc = (struct smem_toc *)(smem_base + smem_size - SMEM_TOC_SIZE);
+
+ if (mmio_read_32((uintptr_t)&toc->identifier) != SMEM_TOC_IDENTIFIER) {
+ return;
+ }
+
+ if (mmio_read_32((uintptr_t)&toc->version) != 1) {
+ ERROR("SMEM: assert failed in %s() at line %d\n",
+ __func__, __LINE__);
+ plat_error_handler(EFAULT);
+ }
+ num_entries = mmio_read_32((uintptr_t)&toc->num_entries);
+ if (num_entries > SMEM_TOC_MAX_SMEM_PARTITIONS) {
+ ERROR("SMEM: assert failed in %s() at line %d\n",
+ __func__, __LINE__);
+ plat_error_handler(EFAULT);
+ }
+
+ smem_info_prt = smem_part_info_alloc(&smem_info_num_prt);
+ if (!smem_info_prt) {
+ ERROR("SMEM: assert failed in %s() at line %d\n",
+ __func__, __LINE__);
+ plat_error_handler(EFAULT);
+ return;
+ }
+
+ for (i = 0, part_indx = 0;
+ i < num_entries && part_indx < smem_info_num_prt; i++) {
+ struct smem_toc_entry *entry = &toc->entry[i];
+ struct smem_partition_info *prt;
+ uint32_t partition_offset;
+ uint32_t size_cacheline;
+ struct smem_partition_header *hdr;
+ uint32_t part_size;
+ struct smem_alloc_params params = { 0 };
+
+ if (!smem_part_is_host_present(entry)) {
+ continue;
+ }
+
+ size = mmio_read_32((uintptr_t)&entry->size);
+ partition_offset = mmio_read_32((uintptr_t)&entry->offset);
+ size_cacheline = mmio_read_32((uintptr_t)&entry->size_cacheline);
+
+ if (partition_offset == 0) {
+ ERROR("SMEM: assert failed in %s() at line %d\n",
+ __func__, __LINE__);
+ plat_error_handler(EFAULT);
+ }
+ if (partition_offset >= smem_size - SMEM_TOC_SIZE) {
+ ERROR("SMEM: assert failed in %s() at line %d\n",
+ __func__, __LINE__);
+ plat_error_handler(EFAULT);
+ }
+
+ hdr = (struct smem_partition_header *)(smem_base +
+ partition_offset);
+
+ if (((unsigned long)hdr & SMEM_PAGE_ALIGN_MASK) != 0) {
+ ERROR("SMEM: assert failed in %s() at line %d\n",
+ __func__, __LINE__);
+ plat_error_handler(EFAULT);
+ }
+ if (hdr->identifier != SMEM_PARTITION_HEADER_ID) {
+ ERROR("SMEM: assert failed in %s() at line %d\n",
+ __func__, __LINE__);
+ plat_error_handler(EFAULT);
+ }
+
+ part_size = mmio_read_32((uintptr_t)&hdr->size);
+ if (part_size != size) {
+ ERROR("SMEM: invalid partition size(%d). Actual "
+ "partition size:%d, part indx: %d\n",
+ part_size, size, i);
+ continue;
+ }
+
+ prt = &smem_info_prt[part_indx++];
+ prt->header = hdr;
+
+ prt->host0 = mmio_read_16((uintptr_t)&hdr->host0);
+ prt->host1 = mmio_read_16((uintptr_t)&hdr->host1);
+
+ prt->size = size;
+ prt->size_cacheline = size_cacheline;
+ prt->offset_free_uncached = sizeof(struct smem_partition_header);
+ prt->offset_free_cached = size;
+
+ params.remote_host = this_host;
+ params.smem_type = SMEM_MEM_FIRST;
+
+ status = smem_part_get_addr_ex(&params);
+
+ if (status != SMEM_STATUS_SUCCESS &&
+ status != SMEM_STATUS_NOT_FOUND) {
+ ERROR("SMEM: partition indx:%d init failed with status 0x%x\n",
+ i, status);
+ plat_error_handler(EFAULT);
+ }
+ }
+}