From ffd00f4998e35f798446a8ace0d730c9ece1e33e Mon Sep 17 00:00:00 2001 From: Nishant Sharma Date: Wed, 21 Feb 2024 14:53:03 +0000 Subject: [PATCH 1/3] mod/noc_s3: add initial framework handlers NoC S3 Network-on-Chip Interconnect is a highly configurable system-level interconnect. This design allows the creation of a highly optimized, non-coherent interconnect which supports Programmable System Address Map, Programmable Access Protection Unit, Programmable Fault Protection Unit and many more. Add initial framework handlers as preparation to add support for the NoC S3 features. The handlers added in this change will be consumed by the subsequent changes. Signed-off-by: Nishant Sharma Change-Id: I81d9d7175a4c1f43dd84e5118772bc8b9d6826d5 --- module/CMakeLists.txt | 1 + module/noc_s3/CMakeLists.txt | 14 ++++ module/noc_s3/Module.cmake | 10 +++ module/noc_s3/include/mod_noc_s3.h | 56 +++++++++++++ module/noc_s3/src/mod_noc_s3.c | 127 +++++++++++++++++++++++++++++ 5 files changed, 208 insertions(+) create mode 100644 module/noc_s3/CMakeLists.txt create mode 100644 module/noc_s3/Module.cmake create mode 100644 module/noc_s3/include/mod_noc_s3.h create mode 100644 module/noc_s3/src/mod_noc_s3.c diff --git a/module/CMakeLists.txt b/module/CMakeLists.txt index 6a0384f89..670f116b7 100644 --- a/module/CMakeLists.txt +++ b/module/CMakeLists.txt @@ -49,6 +49,7 @@ list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/mock_voltage_domain") list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/mpmm") list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/msg_smt") list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/msys_rom") +list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/noc_s3") list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/pcid") list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/perf_controller") list(APPEND SCP_MODULE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/pik_clock") diff --git a/module/noc_s3/CMakeLists.txt b/module/noc_s3/CMakeLists.txt new file mode 100644 index 000000000..48617572f --- /dev/null +++ b/module/noc_s3/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +add_library(${SCP_MODULE_TARGET} SCP_MODULE) + +target_include_directories(${SCP_MODULE_TARGET} + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") + +target_sources( + ${SCP_MODULE_TARGET} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_noc_s3.c") diff --git a/module/noc_s3/Module.cmake b/module/noc_s3/Module.cmake new file mode 100644 index 000000000..d7e54cd06 --- /dev/null +++ b/module/noc_s3/Module.cmake @@ -0,0 +1,10 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +set(SCP_MODULE "noc-s3") + +set(SCP_MODULE_TARGET "module-noc-s3") diff --git a/module/noc_s3/include/mod_noc_s3.h b/module/noc_s3/include/mod_noc_s3.h new file mode 100644 index 000000000..815a00a67 --- /dev/null +++ b/module/noc_s3/include/mod_noc_s3.h @@ -0,0 +1,56 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#ifndef MOD_NOC_S3_H +#define MOD_NOC_S3_H + +#include + +#include + +/*! + * \addtogroup GroupModules Modules + * \{ + */ + +/*! + * \defgroup GroupNoCS3 Network on Chip S3 + * \{ + */ + +/*! + * \brief Platform notification source and notification id. + * + * \details If the module is dependant on notification from other modules, then + * the module will subscribe to its notification and start only after + * receiving it. + */ +struct mod_noc_s3_platform_notification { + /*! Identifier of the notification id */ + const fwk_id_t notification_id; + /*! Identifier of the module sending the notification */ + const fwk_id_t source_id; +}; + +/*! + * \brief Info to configure ports in the NoC S3 block. + */ +struct mod_noc_s3_element_config { + /*! Platform notification source and notification id */ + struct mod_noc_s3_platform_notification plat_notification; +}; + +/*! + * \} + */ + +/*! + * \} + */ + +#endif /* MOD_NOC_S3_H */ diff --git a/module/noc_s3/src/mod_noc_s3.c b/module/noc_s3/src/mod_noc_s3.c new file mode 100644 index 000000000..e5cf94159 --- /dev/null +++ b/module/noc_s3/src/mod_noc_s3.c @@ -0,0 +1,127 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct mod_noc_s3_element_ctx { + /* Points to the configuration of the element. */ + struct mod_noc_s3_element_config *config; +}; + +struct mod_noc_s3_ctx { + /* List of the element's context. */ + struct mod_noc_s3_element_ctx *element_ctx; + /* Number of elements. */ + unsigned int element_count; +}; + +struct mod_noc_s3_ctx noc_s3_ctx; + +static int mod_noc_s3_init( + fwk_id_t module_id, + unsigned int element_count, + const void *unused) +{ + if (element_count == 0) { + /* Configuration will be done during runtime. */ + return FWK_SUCCESS; + } + + noc_s3_ctx.element_ctx = + fwk_mm_calloc(element_count, sizeof(struct mod_noc_s3_element_ctx)); + if (noc_s3_ctx.element_ctx == NULL) { + return FWK_E_NOMEM; + } + + noc_s3_ctx.element_count = element_count; + + return FWK_SUCCESS; +} + +static int mod_noc_s3_element_init( + fwk_id_t element_id, + unsigned int unused, + const void *data) +{ + struct mod_noc_s3_element_config *config; + unsigned int idx; + + config = (struct mod_noc_s3_element_config *)data; + idx = fwk_id_get_element_idx(element_id); + noc_s3_ctx.element_ctx[idx].config = config; + + return FWK_SUCCESS; +} + +static int mod_noc_s3_start(fwk_id_t id) +{ + struct mod_noc_s3_element_config *config; + unsigned int element_id; + int status; + + if (fwk_id_get_type(id) == FWK_ID_TYPE_MODULE) { + return FWK_SUCCESS; + } + + if (!fwk_module_is_valid_element_id(id)) { + return FWK_E_PARAM; + } + + element_id = fwk_id_get_element_idx(id); + config = noc_s3_ctx.element_ctx[element_id].config; + if (!fwk_id_is_equal(config->plat_notification.source_id, FWK_ID_NONE)) { + status = fwk_notification_subscribe( + config->plat_notification.notification_id, + config->plat_notification.source_id, + id); + if (status != FWK_SUCCESS) { + return status; + } + } + + return FWK_SUCCESS; +} + +static int mod_noc_s3_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + struct mod_noc_s3_element_config *config; + unsigned int element_id; + int status; + + element_id = fwk_id_get_element_idx(event->target_id); + config = noc_s3_ctx.element_ctx[element_id].config; + if (fwk_id_is_equal(event->id, config->plat_notification.notification_id)) { + status = fwk_notification_unsubscribe( + event->id, event->source_id, event->target_id); + if (status != FWK_SUCCESS) { + return status; + } + } + + return FWK_SUCCESS; +} + +const struct fwk_module module_noc_s3 = { + .type = FWK_MODULE_TYPE_DRIVER, + .init = mod_noc_s3_init, + .element_init = mod_noc_s3_element_init, + .start = mod_noc_s3_start, + .process_notification = mod_noc_s3_process_notification, +}; -- GitLab From 468ef4467f23ca8c16818bd10f06fed80e24ad32 Mon Sep 17 00:00:00 2001 From: Nishant Sharma Date: Thu, 22 Feb 2024 23:06:49 +0000 Subject: [PATCH 2/3] mod/noc_s3: add the support to discovery node offset The configuration register layout for NoC S3 is laid out in a multibranch tree format. On the top of the tree is global configuration node which points to the domain and domains points to components nodes. The components nodes contains subfeatures that can be used to enable PSAM, APU, FCU and IDM. Add the support to validate the part number of NoC S3 and find the offset address of the configuration registers of all the nodes on the tree and put that into a table. For example: The discovery process will convert the following tree Global CFGNI / \ / \ VD0 VD1 / / PD0 PD1 / / CD0 CD1 / \ / C0 C1 C2 to the following table containing node offset. _______________________________________________ Node Type | Index | Node Id | ---------------|--------|----------------------| | | 0 | 1 | 2 | ---------------|--------|-------|-------|------| Global CFGNI | 0 |CFGNI0 | | | Voltage Domain | 1 |VD0 |VD1 | | Power Domain | 2 |PD0 |PD1 | | CLock Domain | 3 |CD0 |CD1 | | ASNI | 4 |C0 |C1 |C2 | AMNI | 5 | | | | PMU | 6 | | | | HSNI | 7 | | | | HMNI | 8 | | | | PMNI | 9 | | | | As the node id and type are predefined, the offset of the target node is fetched in O(1) by using id and type. To get offset of the C2, the user will access the table using node type 4 and Id 2. Table[4][2] will give offset of C2 Signed-off-by: Nishant Sharma Change-Id: I221694d12a853e46df0f6c434058eaf6dfb4aa2b --- module/noc_s3/CMakeLists.txt | 5 +- module/noc_s3/doc/noc_s3.md | 100 ++++++ module/noc_s3/include/mod_noc_s3.h | 74 +++++ module/noc_s3/src/mod_noc_s3.c | 3 + module/noc_s3/src/noc_s3.c | 60 ++++ module/noc_s3/src/noc_s3.h | 53 ++++ module/noc_s3/src/noc_s3_discovery.c | 450 +++++++++++++++++++++++++++ module/noc_s3/src/noc_s3_discovery.h | 56 ++++ module/noc_s3/src/noc_s3_reg.h | 161 ++++++++++ 9 files changed, 961 insertions(+), 1 deletion(-) create mode 100644 module/noc_s3/doc/noc_s3.md create mode 100644 module/noc_s3/src/noc_s3.c create mode 100644 module/noc_s3/src/noc_s3.h create mode 100644 module/noc_s3/src/noc_s3_discovery.c create mode 100644 module/noc_s3/src/noc_s3_discovery.h create mode 100644 module/noc_s3/src/noc_s3_reg.h diff --git a/module/noc_s3/CMakeLists.txt b/module/noc_s3/CMakeLists.txt index 48617572f..a2834f618 100644 --- a/module/noc_s3/CMakeLists.txt +++ b/module/noc_s3/CMakeLists.txt @@ -11,4 +11,7 @@ target_include_directories(${SCP_MODULE_TARGET} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") target_sources( - ${SCP_MODULE_TARGET} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_noc_s3.c") + ${SCP_MODULE_TARGET} + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_noc_s3.c" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/noc_s3.c" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/noc_s3_discovery.c") diff --git a/module/noc_s3/doc/noc_s3.md b/module/noc_s3/doc/noc_s3.md new file mode 100644 index 000000000..6afac675e --- /dev/null +++ b/module/noc_s3/doc/noc_s3.md @@ -0,0 +1,100 @@ +\ingroup GroupModules Modules +\defgroup GroupNoCS3 NoCS3 + +# Module NoCS3 Architecture + +Copyright (c) 2024, Arm Limited. All rights reserved. + +# Overview + +NoC S3 Network-on-Chip Interconnect is a highly configurable system-level +interconnect. This design allows the creation of a highly optimized, +non-coherent interconnect which supports Programmable System Address Map, +Programmable Access Protection Unit, Programmable Fault Protection Unit and +many more. + +```ditaa {cmd=true args=["-E"]} + +---------------------------------------+ + | NoC S3 | + | | + | | + | | + | Completer Node Requester Node | + | for Requester For completer | + +------------+ | +----------+ +-----------+ | +-----------+ + | | | | | | | | | | + | Requester +-->+-->|xSNI Node +-- -- --+ xMNI Node +-->+--->| Completer | + | | | | | | | | | | + +------------+ | | PSAM/APU | | APU | | +-----------+ + | +----------+ +-----------+ | + | | + | | + | | + | | + | | + +---------------------------------------+ +``` + +This module implements the driver for NoC S3 runtime configuration. + +# Module Design + +This module provides an interface for other modules to configure a given NoC S3 +instance during runtime. The module does that in 2 stages. +1) Node Discovery +2) PSAM programming + +# Node Discovery + +The configuration register layout for NoC S3 is laid out in a multibranch tree +format. On the top of the tree is global configuration node which points to the +domain and domains points to components nodes. The components nodes contains +subfeatures that can be used to enable PSAM, APU, FCU and IDM. + +The discovery process is designed to traverse a tree and convert the node data +into a table for easy and optimized access in O(1) time. The table is used to +store the offset of each node. During the discovery process, the tree is parsed +twice. The first time it is parsed, the maximum row size for all node types is +found, and during the second pass, the offset for each discovered node type is +recorded. The node IDs within the same types are linear, and the maximum node +ID can be used to determine the total number of nodes. + +The discovery process will convert the following tree +```ditaa {cmd=true args=["-E"]} + Global CFGNI + / \ + / \ + VD0 VD1 + / / + PD0 PD1 + / / + CD0 CD1 + / \ / + C0 C1 C2 + +``` +To the following table containing node offset. +```ditaa {cmd=true args=["-E"]} + _______________________________________________ +|Node Type | Index | Node Id | +|---------------|--------|----------------------| +| | | 0 | 1 | 2 | +|---------------|--------|-------|-------|------| +|Global CFGNI | 0 |CFGNI0 | | | +|Voltage Domain | 1 |VD0 |VD1 | | +|Power Domain | 2 |PD0 |PD1 | | +|CLock Domain | 3 |CD0 |CD1 | | +|ASNI | 4 |C0 |C1 |C2 | +|AMNI | 5 | | | | +|PMU | 6 | | | | +|HSNI | 7 | | | | +|HMNI | 8 | | | | +|PMNI | 9 | | | | +|_______________|________|_______|_______|______| +``` + +As the node id and type are predefined, the offset of the target node is +fetch in O(1) by using id and type. +Example: To get offset of the C2, the user will access the table using node +type 4 and Id 2. Table[4][2] will give offset of C2. + diff --git a/module/noc_s3/include/mod_noc_s3.h b/module/noc_s3/include/mod_noc_s3.h index 815a00a67..d7ee11ce6 100644 --- a/module/noc_s3/include/mod_noc_s3.h +++ b/module/noc_s3/include/mod_noc_s3.h @@ -23,6 +23,78 @@ * \{ */ +/*! + * \brief NoC S3 node type enumerations. + */ +enum mod_noc_s3_node_type { + /* Domains */ + /*! NoC S3 node type Global CFGNI Domain. */ + MOD_NOC_S3_NODE_TYPE_GLOBAL_CFGNI, + /*! NoC S3 node type Voltage Domain. */ + MOD_NOC_S3_NODE_TYPE_VD, + /*! NoC S3 node type Power Domain. */ + MOD_NOC_S3_NODE_TYPE_PD, + /*! NoC S3 node type Clock Domain. */ + MOD_NOC_S3_NODE_TYPE_CD, + /* Components */ + /*! NoC S3 node type ASNI. */ + MOD_NOC_S3_NODE_TYPE_ASNI, + /*! NoC S3 node type AMNI. */ + MOD_NOC_S3_NODE_TYPE_AMNI, + /*! NoC S3 node type PMU. */ + MOD_NOC_S3_NODE_TYPE_PMU, + /*! NoC S3 node type HSNI. */ + MOD_NOC_S3_NODE_TYPE_HSNI, + /*! NoC S3 node type HMNI. */ + MOD_NOC_S3_NODE_TYPE_HMNI, + /*! NoC S3 node type PMNI. */ + MOD_NOC_S3_NODE_TYPE_PMNI, + /*! Maximum number of nodes. */ + MOD_NOC_S3_NODE_TYPE_COUNT +}; + +/*! + * \brief NoC S3 configuration node granularity enumeration + */ +enum noc_s3_granularity { + /*! NoC S3 Node granularity 4K. */ + NOC_S3_4KB_CONFIG_NODES, + /*! NoC S3 Node granularity 64K. */ + NOC_S3_64KB_CONFIG_NODES, +}; + +/*! + * \brief NoC S3 discovery data. + */ +struct noc_s3_discovery_data { + /* Offset table. */ + uintptr_t *table[MOD_NOC_S3_NODE_TYPE_COUNT]; + /* Size of each row, for each node type in the offset table. */ + uint8_t max_node_size[MOD_NOC_S3_NODE_TYPE_COUNT]; +}; + +/*! + * \brief NoC S3 device structure + */ +struct mod_noc_s3_dev { + /*! NoC S3 periphbase address, same as CFGNI0 address. */ + uintptr_t periphbase; + /*! + * The memory-mapped registers of NoC S3 are organized in a series of 4KB or + * 64KB regions. Specify whether it has 4KB or 64KB config nodes. + */ + enum noc_s3_granularity node_granularity; + /*! + * Points to the table generated during discovery. + */ + struct noc_s3_discovery_data discovery_data; + /*! + * Flag to indicate that discovery is performed and the table is + * initialized. + */ + bool discovery_completed; +}; + /*! * \brief Platform notification source and notification id. * @@ -41,6 +113,8 @@ struct mod_noc_s3_platform_notification { * \brief Info to configure ports in the NoC S3 block. */ struct mod_noc_s3_element_config { + /*! NoC S3 periphbase address, same as CFGNI0 address. */ + uintptr_t periphbase; /*! Platform notification source and notification id */ struct mod_noc_s3_platform_notification plat_notification; }; diff --git a/module/noc_s3/src/mod_noc_s3.c b/module/noc_s3/src/mod_noc_s3.c index e5cf94159..4fa71a3bc 100644 --- a/module/noc_s3/src/mod_noc_s3.c +++ b/module/noc_s3/src/mod_noc_s3.c @@ -21,6 +21,8 @@ struct mod_noc_s3_element_ctx { /* Points to the configuration of the element. */ struct mod_noc_s3_element_config *config; + /* NoC S3 device handler. */ + struct mod_noc_s3_dev noc_s3_dev; }; struct mod_noc_s3_ctx { @@ -64,6 +66,7 @@ static int mod_noc_s3_element_init( config = (struct mod_noc_s3_element_config *)data; idx = fwk_id_get_element_idx(element_id); noc_s3_ctx.element_ctx[idx].config = config; + noc_s3_ctx.element_ctx[idx].noc_s3_dev.periphbase = config->periphbase; return FWK_SUCCESS; } diff --git a/module/noc_s3/src/noc_s3.c b/module/noc_s3/src/noc_s3.c new file mode 100644 index 000000000..924275e52 --- /dev/null +++ b/module/noc_s3/src/noc_s3.c @@ -0,0 +1,60 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#include +#include +#include + +#include + +#include +#include + +#include + +/* Check if the node_type is of node_type domain. */ +bool noc_s3_is_node_type_domain(enum mod_noc_s3_node_type node_type) +{ + bool is_domain; + + switch (node_type) { + case MOD_NOC_S3_NODE_TYPE_GLOBAL_CFGNI: + case MOD_NOC_S3_NODE_TYPE_VD: + case MOD_NOC_S3_NODE_TYPE_PD: + case MOD_NOC_S3_NODE_TYPE_CD: + is_domain = true; + break; + default: + is_domain = false; + break; + }; + + return is_domain; +} + +/* Check if the node_type is of node_type component. */ +bool noc_s3_is_node_type_component(enum mod_noc_s3_node_type node_type) +{ + bool is_component; + + switch (node_type) { + case MOD_NOC_S3_NODE_TYPE_ASNI: + case MOD_NOC_S3_NODE_TYPE_AMNI: + case MOD_NOC_S3_NODE_TYPE_PMU: + case MOD_NOC_S3_NODE_TYPE_HSNI: + case MOD_NOC_S3_NODE_TYPE_HMNI: + case MOD_NOC_S3_NODE_TYPE_PMNI: + is_component = true; + break; + default: + is_component = false; + break; + }; + + return is_component; +} diff --git a/module/noc_s3/src/noc_s3.h b/module/noc_s3/src/noc_s3.h new file mode 100644 index 000000000..f1cbb9571 --- /dev/null +++ b/module/noc_s3/src/noc_s3.h @@ -0,0 +1,53 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#ifndef NOC_S3_H +#define NOC_S3_H + +#include + +#include +#include + +#define MOD_NAME "[NOC_S3] " + +/*! + * \brief NoC S3 component subfeature type enumerations. + */ +enum noc_s3_subfeature_type { + /*! NoC S3 subfeature Access Protection Unit. */ + NOC_S3_NODE_TYPE_APU, + /*! NoC S3 subfeature Programable System Address Map. */ + NOC_S3_NODE_TYPE_PSAM, + /*! NoC S3 subfeature Functional Crossbar Unit. */ + NOC_S3_NODE_TYPE_FCU, + /*! NoC S3 subfeature Interconnect Device Management. */ + NOC_S3_NODE_TYPE_IDM, +}; + +/*! + * \brief Check if the node type is of type domain. + * + * \param[in] node_type Node type. + * + * \return True if node is a domain. + * \return False if node is not a domain. + */ +bool noc_s3_is_node_type_domain(enum mod_noc_s3_node_type node_type); + +/*! + * \brief Check if the node type is of type component. + * + * \param[in] node_type Node type. + * + * \return True if node is a component. + * \return False if node is not a component. + */ +bool noc_s3_is_node_type_component(enum mod_noc_s3_node_type node_type); + +#endif /* NOC_S3_H */ diff --git a/module/noc_s3/src/noc_s3_discovery.c b/module/noc_s3/src/noc_s3_discovery.c new file mode 100644 index 000000000..0d94b11e6 --- /dev/null +++ b/module/noc_s3/src/noc_s3_discovery.c @@ -0,0 +1,450 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * NoC S3 Node Discovery Support. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * Helpers to extract Node ID and Node type from node_type register in the + * header configuration. + */ +#define GET_NODE_TYPE(node_type) \ + ((node_type & NOC_S3_DOMAIN_NODE_TYPE_MSK) >> NOC_S3_DOMAIN_NODE_TYPE_POS) +#define GET_NODE_ID(node_type) \ + ((node_type & NOC_S3_DOMAIN_NODE_ID_MSK) >> NOC_S3_DOMAIN_NODE_ID_POS) + +/* + * The NoC S3 specification defines the following levels. + * CFGNI->VD->PD->CD->Component + * + * This means while doing DFS, At MAX only 5 context needs to be stored because + * the depth of the tree is limited to 5. + */ +#define NOC_S3_TREE_DEPTH 5U + +/* List the stages in the discovery process. */ +enum noc_s3_discovery_stage { + /* NoC S3 Discovery stage to determine the row size. */ + NOC_S3_DISCOVERY_STAGE_DETERMINE_ROW_SIZE, + /* NoC S3 Discovery stage to record offset for each node. */ + NOC_S3_DISCOVERY_STAGE_RECORD_OFFSET, +}; + +struct noc_s3_discovery_node_context { + struct noc_s3_domain_cfg_hdr *domain_node; + uint32_t children_processed; +}; + +/* + * Helper to extract part ID from global configuration node's peripheral_id0 and + * peripheral_id1 registers + */ +static uint16_t get_part_id(uint32_t peripheral_id0, uint32_t peripheral_id1) +{ + uint8_t part_num_l; + uint8_t part_num_h; + + part_num_l = peripheral_id0 & NOC_S3_GLOBAL_CFG_PERIPHERAL_ID0_MASK; + part_num_h = peripheral_id1 & NOC_S3_GLOBAL_CFG_PERIPHERAL_ID1_MASK; + + return ((uint16_t)part_num_h + << NOC_S3_GLOBAL_CFG_PERIPHERAL_ID0_PART_NUM_WIDTH) | + part_num_l; +} + +/* + * Check if the domain node is of type global cfgni. The discovery can only + * start from the global cfgni node. + */ +static bool is_global_cfgni_node(uintptr_t periphbase) +{ + struct noc_s3_global_reg *reg; + + reg = (struct noc_s3_global_reg *)periphbase; + return (GET_NODE_TYPE(reg->node_type) == MOD_NOC_S3_NODE_TYPE_GLOBAL_CFGNI); +} + +/* + * This driver only supports NoC S3. Check the part number to ensure that the + * correct device is being initialized. + */ +static bool is_part_number_supported(uintptr_t periphbase) +{ + struct noc_s3_global_reg *reg; + uint16_t part_id; + + reg = (struct noc_s3_global_reg *)periphbase; + part_id = get_part_id(reg->peripheral_id0, reg->peripheral_id1); + return part_id == NOC_S3_PART_NUMBER; +} + +/* + * The FMU node on NoC S3 does not have a node_type field. This type of node can + * only be identified by reading part number register in the node's + * configuration space. + */ +static bool is_fmu_node(struct mod_noc_s3_dev *dev, uint32_t offset) +{ + struct noc_s3_fmu_reg *reg; + + /* + * NoC S3 permits 4KB or 64KB configuration nodes. FMU check requires + * reading at 0xFFE0 offset which can only be done for 64KB config node + * granularity mode. + */ + if (dev->node_granularity == NOC_S3_4KB_CONFIG_NODES) { + return false; + } + + /* + * Read the FMU part number to check if the node is FMU. + */ + reg = (struct noc_s3_fmu_reg *)(dev->periphbase + offset); + if (reg->fmu_errpidr0 != 0) { + return true; + } + + return false; +} + +/* + * NoC S3 permits 4KB and 64KB configuration nodes. This can be identified + * during the runtime by reading the offset of the first node after global + * configuration node. In this case, it is Voltage domain 0. + */ +static void determine_node_granularity(struct mod_noc_s3_dev *dev) +{ + struct noc_s3_global_reg *reg; + + /* + * Compare the offset of the first voltage domain to check the size of + * configuration node. + */ + reg = (struct noc_s3_global_reg *)dev->periphbase; + if (reg->vd_pointers == (64 * FWK_KIB)) { + dev->node_granularity = NOC_S3_64KB_CONFIG_NODES; + } else { + dev->node_granularity = NOC_S3_4KB_CONFIG_NODES; + } +} + +/* Perform a series of check to validate the device. */ +static int validate_device(struct mod_noc_s3_dev *dev) +{ + if (dev->periphbase == 0) { + return FWK_E_PARAM; + } + + /* Discovery starts from the CFGNI node. */ + if (!is_global_cfgni_node(dev->periphbase)) { + return FWK_E_PARAM; + } + + if (!is_part_number_supported(dev->periphbase)) { + return FWK_E_SUPPORT; + } + + return FWK_SUCCESS; +} + +/* + * This API populate the discovery data base on the stage of the discovery. + */ +static int fill_discovery_data( + struct mod_noc_s3_dev *dev, + struct noc_s3_discovery_data *discovery_data, + struct noc_s3_domain_cfg_hdr *domain_node, + enum noc_s3_discovery_stage stage) +{ + uint16_t node_id; + uint16_t node_type; + + node_type = GET_NODE_TYPE(domain_node->node_type); + node_id = GET_NODE_ID(domain_node->node_type); + FWK_TRACE(MOD_NAME "Found Node Type: %d, Node ID: %d", node_type, node_id); + switch (stage) { + /* + * For each node type, the Node IDs are numbered sequentially. That + * means the maximum Node ID + 1 is the size of the row for each + * node type. + */ + case NOC_S3_DISCOVERY_STAGE_DETERMINE_ROW_SIZE: + discovery_data->max_node_size[node_type] = + FWK_MAX(discovery_data->max_node_size[node_type], node_id + 1); + break; + /* Record the node offset in the table */ + case NOC_S3_DISCOVERY_STAGE_RECORD_OFFSET: + discovery_data->table[node_type][node_id] = + (uintptr_t)domain_node - dev->periphbase; + break; + default: + return FWK_E_PARAM; + break; + }; + + return FWK_SUCCESS; +} + +/* + * This function performs a depth first walk of the node tree and can be + * invoked to either count the type of each node found during the walk or to + * record the offsets of the node in the discovery data table. The 'stage' + * parameter can be used to choose the desired operation during the tree walk. + */ +static int discover_nodes( + struct mod_noc_s3_dev *dev, + struct noc_s3_discovery_data *discovery_data, + enum noc_s3_discovery_stage stage) +{ + struct noc_s3_discovery_node_context node_context[NOC_S3_TREE_DEPTH] = { + 0 + }; + struct noc_s3_domain_cfg_hdr *domain_node; + uint32_t *children_processed; + uint32_t child_offset; + uint32_t node_context_size; + int status; + + domain_node = (struct noc_s3_domain_cfg_hdr *)dev->periphbase; + + /* Allocate the first node entry and push to the stack. */ + node_context_size = 0; + node_context[node_context_size].domain_node = domain_node; + node_context[node_context_size].children_processed = 0; + node_context_size++; + + while (node_context_size > 0) { + domain_node = node_context[node_context_size - 1].domain_node; + children_processed = + &node_context[node_context_size - 1].children_processed; + + /* + * Only domains have children. Check if node type is component to + * process. + */ + if (!noc_s3_is_node_type_domain( + GET_NODE_TYPE(domain_node->node_type)) || + /* Stop if all the children are processed. */ + *children_processed >= domain_node->child_node_info) { + status = + fill_discovery_data(dev, discovery_data, domain_node, stage); + if (status != FWK_SUCCESS) { + FWK_LOG_ERR(MOD_NAME "Discovery Data fill failed."); + return status; + } + + /* This element is processed. Continue with the parent. */ + node_context_size--; + continue; + } + + /* + * Check if the last node was FMU node and all the children are + * processed. + */ + child_offset = domain_node->x_pointers[*children_processed]; + while ((*children_processed < domain_node->child_node_info) && + is_fmu_node(dev, child_offset)) { + *children_processed += 1; + child_offset = domain_node->x_pointers[*children_processed]; + } + + if (*children_processed >= domain_node->child_node_info) { + continue; + } + + /* Push the next child to the context. */ + if (node_context_size < NOC_S3_TREE_DEPTH) { + *children_processed += 1; + node_context_size += 1; + node_context[node_context_size - 1].domain_node = + (struct noc_s3_domain_cfg_hdr + *)(dev->periphbase + child_offset); + node_context[node_context_size - 1].children_processed = 0; + } else { + /* Depth of more than 5 is not supported. */ + fwk_unexpected(); + return FWK_E_SUPPORT; + } + } + + return FWK_SUCCESS; +} + +/* Allocate rows of nodes for each node type in the discovery table. */ +static int allocate_nodes(struct noc_s3_discovery_data *discovery_data) +{ + enum mod_noc_s3_node_type node_type; + uintptr_t *table_node_ptr; + uint8_t max_node_size; + + for (node_type = MOD_NOC_S3_NODE_TYPE_GLOBAL_CFGNI; + node_type < MOD_NOC_S3_NODE_TYPE_COUNT; + node_type++) { + max_node_size = discovery_data->max_node_size[node_type]; + if (max_node_size == 0) { + /* Node is not present. No need for allocation. */ + continue; + } + + /* Row size is +1 to the max node id because ids starts from 0. */ + table_node_ptr = fwk_mm_calloc(max_node_size, sizeof(uintptr_t)); + if (table_node_ptr == NULL) { + return FWK_E_NOMEM; + } + + discovery_data->table[node_type] = table_node_ptr; + } + + return FWK_SUCCESS; +} + +/* + * The component node contains a list of the subfeatures it supports. This API + * parses the list and records the offset of the target subfeature configuration + * registers. + */ +int noc_s3_get_subfeature_offset( + struct noc_s3_component_cfg_hdr *component_hdr, + enum noc_s3_subfeature_type subfeature_type, + uint32_t *ret_off_addr) +{ + uint32_t s_idx; + + if (!noc_s3_is_node_type_component( + GET_NODE_TYPE(component_hdr->node_type))) { + FWK_LOG_ERR(MOD_NAME "Invalid component header"); + return FWK_E_PARAM; + } + + for (s_idx = 0; s_idx < component_hdr->num_subfeatures; s_idx++) { + if (component_hdr->subfeature[s_idx].type == subfeature_type) { + *ret_off_addr = component_hdr->subfeature[s_idx].pointer; + return FWK_SUCCESS; + } + } + + FWK_LOG_ERR( + MOD_NAME "Subfeature(%d) not supported by the Node[Type: %" PRId32 + "][ID: %" PRId32 "]", + subfeature_type, + GET_NODE_TYPE(component_hdr->node_type), + GET_NODE_ID(component_hdr->node_type)); + return FWK_E_SUPPORT; +} + +/* + * This API is designed to traverse a tree and convert the node data into a + * table for easy and optimized access in O(1) time. The table is used to store + * the offset of each node. During the discovery process, the tree is parsed + * twice. The first time it is parsed, the maximum row size for all node types + * is found, and during the second pass, the offset for each discovered node + * type is recorded. The node IDs within the same types are linear, and the + * maximum node ID can be used to determine the total number of nodes. + * + * The discovery will convert the following tree + * Global CFGNI + * / \ + * / \ + * VD0 VD1 + * / / + * PD0 PD1 + * / / + * CD0 CD1 + * / \ / + * C0 C1 C2 + * + * To the following table containing node offset. + * _______________________________________________ + * |Node Type | Index | Node Id | + * |---------------|--------|----------------------| + * | | | 0 | 1 | 2 | + * |---------------|--------|-------|-------|------| + * |Global CFGNI | 0 |CFGNI0 | | | + * |Voltage Domain | 1 |VD0 |VD1 | | + * |Power Domain | 2 |PD0 |PD1 | | + * |CLock Domain | 3 |CD0 |CD1 | | + * |ASNI | 4 |C0 |C1 |C2 | + * |AMNI | 5 | | | | + * |PMU | 6 | | | | + * |HSNI | 7 | | | | + * |HMNI | 8 | | | | + * |PMNI | 9 | | | | + * |_______________|________|_______|_______|______| + * + * As the node id and type are predefined, the offset of the target node is + * fetch in O(1) by using id and type. + * Example: To get offset of the C2, the user will access the table using node + * type 4 and Id 2. Table[4][2] will give offset of C2 + */ +int noc_s3_discovery(struct mod_noc_s3_dev *dev) +{ + struct noc_s3_discovery_data *discovery_data; + int err; + + if (dev == NULL) { + return FWK_E_PARAM; + } + + err = validate_device(dev); + if (err != FWK_SUCCESS) { + FWK_LOG_ERR(MOD_NAME " Device validation failed."); + return err; + } + + determine_node_granularity(dev); + + if (dev->discovery_completed) { + return FWK_SUCCESS; + } + + /* + * Find the maximum node IDs for all the node types. This information is + * used to allocate rows in the table. + */ + discovery_data = &dev->discovery_data; + err = discover_nodes( + dev, discovery_data, NOC_S3_DISCOVERY_STAGE_DETERMINE_ROW_SIZE); + if (err != FWK_SUCCESS) { + return err; + } + + /* + * Allocate the rows to store node information for node types discovered + * during the process. + */ + err = allocate_nodes(discovery_data); + if (err != FWK_SUCCESS) { + return err; + } + + /* Record in the offset value for each node. */ + err = discover_nodes( + dev, discovery_data, NOC_S3_DISCOVERY_STAGE_RECORD_OFFSET); + if (err != FWK_SUCCESS) { + return err; + } + + /* Update the device handler. */ + dev->discovery_completed = true; + + return err; +} diff --git a/module/noc_s3/src/noc_s3_discovery.h b/module/noc_s3/src/noc_s3_discovery.h new file mode 100644 index 000000000..5384844f0 --- /dev/null +++ b/module/noc_s3/src/noc_s3_discovery.h @@ -0,0 +1,56 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#ifndef NOC_S3_DISCOVERY_H +#define NOC_S3_DISCOVERY_H + +#include +#include + +#include + +#include + +/** + * \brief Parse the component header to find the target subfeature. + * + * \details Component header contains a list of subfeatures that it supports. + * This API parses the list and then records the offset of the + * subfeature. + * + * \param[in] component_hdr Component header address. + * \param[in] subfeature_type Type of the subfeature. + * \param[out] ret_off_addr Offset of the subfeature. + * + * \return FWK_E_PARAM If the component header is not of type component. + * \return FWK_E_SUPPORT If the type of subfeature is not supported. + * \return FWK_SUCCESS If the subfeature offset is found. + */ +int noc_s3_get_subfeature_offset( + struct noc_s3_component_cfg_hdr *component_hdr, + enum noc_s3_subfeature_type subfeature_type, + uint32_t *ret_off_addr); + +/** + * \brief Performs a discovery flow on the given NoC S3 instance. + * + * \details In NoC S3, "Discovery" is the process of finding the offset of all + * the nodes present on the discovery tree. This API performs two stage + * walk of the tree. In the first stage, a depth first search is + * performed on the tree to find the number of nodes for each node + * type. In the second stage, another depth first search is performed + * to record the node offset in the table. + * + * \param[in, out] dev NoC S3 Device handler struct \ref noc_s3.h + * + * \return FWK_SUCCESS If the discovery is successful. + * \return FWK_E_PARAM If any of the input parameter is invalid. + */ +int noc_s3_discovery(struct mod_noc_s3_dev *dev); + +#endif /* NOC_S3_DISCOVERY_H */ diff --git a/module/noc_s3/src/noc_s3_reg.h b/module/noc_s3/src/noc_s3_reg.h new file mode 100644 index 000000000..4a774ad2a --- /dev/null +++ b/module/noc_s3/src/noc_s3_reg.h @@ -0,0 +1,161 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#ifndef NOC_S3_REG_H +#define NOC_S3_REG_H + +#include + +#include +#include +#include + +// clang-format off +/*! + * \brief Offset address of the last register in FMU. + */ +#define FMU_ERRCIDR3_OFFSET (0xFFFC) + +/*! + * \brief NoC S3 FMU registers + */ +struct noc_s3_fmu_reg { + FWK_R uint64_t fmu_err_fr_0; + FWK_RW uint64_t fmu_err_ctlr_0; + FWK_RW uint64_t fmu_err_status; + FWK_RW uint64_t fmu_err_addr; + FWK_RW uint64_t fmu_err_misc0; + FWK_RW uint64_t fmu_err_misc1; + const uint32_t reserved_0[4]; + FWK_R uint64_t fmu_err_fr; + FWK_R uint64_t fmu_err_ctlr; + const uint32_t reserved_1[14316]; + FWK_R uint64_t fmu_errgsr; + const uint32_t reserved_2[62]; + FWK_R uint32_t fmu_erriidr; + const uint32_t reserved_3[63]; + FWK_RW uint32_t fmu_key; + FWK_RW uint32_t fmu_smen; + FWK_RW uint32_t fmu_sminjerr; + const uint32_t reserved_4; + FWK_RW uint64_t fmu_sminfo; + const uint32_t reserved_5[122]; + FWK_RW uint32_t fmu_erracr; + const uint32_t reserved_6[1774]; + FWK_R uint32_t fmu_errdevarch; + const uint32_t reserved_7[2]; + FWK_R uint32_t fmu_errdevid; + const uint32_t reserved_8; + FWK_R uint32_t fmu_errpidr4; + const uint32_t reserved_9[3]; + FWK_R uint32_t fmu_errpidr0; + FWK_R uint32_t fmu_errpidr1; + FWK_R uint32_t fmu_errpidr2; + FWK_R uint32_t fmu_errpidr3; + FWK_R uint32_t fmu_errcidr0; + FWK_R uint32_t fmu_errcidr1; + FWK_R uint32_t fmu_errcidr2; + FWK_R uint32_t fmu_errcidr3; +}; + +static_assert( + FMU_ERRCIDR3_OFFSET == offsetof(struct noc_s3_fmu_reg, fmu_errcidr3)); + +/*! + * \brief Interconnect Part Number for NoC S3. + */ + +#define NOC_S3_PID0_PART_NUM (0x3F) +#define NOC_S3_PID1_PART_NUM (0x4) + +/*! + * \brief NoC S3 Domain top registers. These are common for all domains. + */ +struct noc_s3_domain_cfg_hdr { + FWK_R uint32_t node_type; + FWK_R uint32_t child_node_info; + FWK_R uint32_t x_pointers[]; +}; + +/*! Field definitions for node_type register. */ +#define NOC_S3_DOMAIN_NODE_TYPE_POS (0U) +#define NOC_S3_DOMAIN_NODE_TYPE_MSK \ + (0xFFFFUL << NOC_S3_DOMAIN_NODE_TYPE_POS) +#define NOC_S3_DOMAIN_NODE_TYPE NOC_S3_DOMAIN_NODE_TYPE_MSK +#define NOC_S3_DOMAIN_NODE_ID_POS (16U) +#define NOC_S3_DOMAIN_NODE_ID_MSK (0xFFFFUL << NOC_S3_DOMAIN_NODE_ID_POS) +#define NOC_S3_DOMAIN_NODE_ID NOC_S3_DOMAIN_NODE_ID_MSK + +/*! Offset of first pointer register in the component configuration header. */ +#define COMPONENT_CFG_HEADER_SUBFEATURE_OFFSET (0x108) + +/*! + * \brief NoC S3 Component top registers. These are common for all components. + */ +struct noc_s3_component_cfg_hdr { + FWK_R uint32_t node_type; + const uint32_t reserved_0[4]; + FWK_R uint32_t interface_id_0_3; + const uint32_t reserved_1[58]; + FWK_R uint32_t num_subfeatures; + const uint32_t reserved_2; + struct { + FWK_R uint32_t type; + FWK_R uint32_t pointer; + } subfeature[]; +}; + +static_assert( + COMPONENT_CFG_HEADER_SUBFEATURE_OFFSET == + offsetof(struct noc_s3_component_cfg_hdr, subfeature)); + +/*! Offset of the last register in the Global register definition. */ +#define COMPONENT_ID3_OFFSET (0xFFC) + +/* + * Definitions for Global configuration peripheral id register width and shift. + */ +#define NOC_S3_GLOBAL_CFG_PERIPHERAL_ID0_PART_NUM_WIDTH 8U +#define NOC_S3_GLOBAL_CFG_PERIPHERAL_ID0_PART_NUM_SHIFT 0U +#define NOC_S3_GLOBAL_CFG_PERIPHERAL_ID0_MASK (0x3FU) +#define NOC_S3_GLOBAL_CFG_PERIPHERAL_ID1_MASK (0xFU) +#define NOC_S3_PART_NUMBER 0x43F + +/*! + * \brief NoC S3 Global register. + */ +struct noc_s3_global_reg { + FWK_R uint32_t node_type; + /* Number of child nodes. */ + FWK_R uint32_t child_node_info; + FWK_R uint32_t vd_pointers; + const uint32_t reserved1[959]; + FWK_RW uint32_t secure_control; + FWK_RW uint32_t root_control; + const uint32_t reserved2[48]; + FWK_R uint32_t peripheral_id4; + FWK_R uint32_t peripheral_id5; + FWK_R uint32_t peripheral_id6; + FWK_R uint32_t peripheral_id7; + FWK_R uint32_t peripheral_id0; + FWK_R uint32_t peripheral_id1; + FWK_R uint32_t peripheral_id2; + FWK_R uint32_t peripheral_id3; + FWK_R uint32_t component_id0; + FWK_R uint32_t component_id1; + FWK_R uint32_t component_id2; + FWK_R uint32_t component_id3; +}; + +static_assert( + COMPONENT_ID3_OFFSET == + offsetof(struct noc_s3_global_reg, component_id3)); + +// clang-format on + +#endif /* NOC_S3_REG_H */ -- GitLab From ed7e163e111535789250ebb76607bc8028163382 Mon Sep 17 00:00:00 2001 From: Nishant Sharma Date: Tue, 27 Feb 2024 21:38:48 +0000 Subject: [PATCH 3/3] mod/noc_s3: add the support to program psam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Programmable System Address Map feature in NoC S3 allows configurable address map for address‑based routing from each upstream interface to the corresponding downstream interfaces. Add the support to configure PSAM during runtime in following mode 1) Static mapping with the information passed through module config. It is expected that the statically mapped regions will not change during the runtime. Their context is handled by the module. 2) Mapping/Unmapping requested through the APIs exposed by the module. These APIs are expected to be used by the modules that will change the mapping during the runtime and modules will do additional steps such as mapping peripheral base in the address translation unit before calling the API. For this reason, the module will maintain the context for the blocks that it is managing. Signed-off-by: Nishant Sharma Change-Id: Ia2c08a76e5d48ecdb6b38d19180e015aca22dc41 --- module/noc_s3/CMakeLists.txt | 3 +- module/noc_s3/doc/noc_s3.md | 17 ++ module/noc_s3/include/mod_noc_s3.h | 111 +++++++- module/noc_s3/src/mod_noc_s3.c | 49 ++++ module/noc_s3/src/noc_s3.c | 110 +++++++ module/noc_s3/src/noc_s3.h | 59 ++++ module/noc_s3/src/noc_s3_psam.c | 444 +++++++++++++++++++++++++++++ module/noc_s3/src/noc_s3_psam.h | 77 +++++ module/noc_s3/src/noc_s3_reg.h | 42 +++ 9 files changed, 908 insertions(+), 4 deletions(-) create mode 100644 module/noc_s3/src/noc_s3_psam.c create mode 100644 module/noc_s3/src/noc_s3_psam.h diff --git a/module/noc_s3/CMakeLists.txt b/module/noc_s3/CMakeLists.txt index a2834f618..a9ae4d547 100644 --- a/module/noc_s3/CMakeLists.txt +++ b/module/noc_s3/CMakeLists.txt @@ -14,4 +14,5 @@ target_sources( ${SCP_MODULE_TARGET} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_noc_s3.c" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/noc_s3.c" - PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/noc_s3_discovery.c") + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/noc_s3_discovery.c" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/noc_s3_psam.c") diff --git a/module/noc_s3/doc/noc_s3.md b/module/noc_s3/doc/noc_s3.md index 6afac675e..3fc70d4a0 100644 --- a/module/noc_s3/doc/noc_s3.md +++ b/module/noc_s3/doc/noc_s3.md @@ -98,3 +98,20 @@ fetch in O(1) by using id and type. Example: To get offset of the C2, the user will access the table using node type 4 and Id 2. Table[4][2] will give offset of C2. +# PSAM Programming + +Programmable System Address Map feature in NoC S3 allows configurable address +map for address‑based routing from each upstream interface to the corresponding +downstream interfaces. + +The module can program PSAM in the following ways. + +1) Static mapping with the information passed through module config. It is +expected that the statically mapped regions will not change during the runtime. +Their context is handled by the module. + +2) Mapping/Unmapping requested through the APIs exposed by the module. These +APIs are expected to be used by the modules that will change the mapping during +the runtime and modules will do additional steps such as mapping peripheral +base in the address translation unit before calling the API. For this reason, +the module will maintain the context for the blocks that it is managing. diff --git a/module/noc_s3/include/mod_noc_s3.h b/module/noc_s3/include/mod_noc_s3.h index d7ee11ce6..0baf80bac 100644 --- a/module/noc_s3/include/mod_noc_s3.h +++ b/module/noc_s3/include/mod_noc_s3.h @@ -22,6 +22,27 @@ * \defgroup GroupNoCS3 Network on Chip S3 * \{ */ +/* + * +---------------------------------------+ + * | NoC S3 | + * | | + * | | + * | | + * | Completer Node Requester Node | + * | for Requester For completer | + * +------------+ | +----------+ +-----------+ | +-----------+ + * | | | | | | | | | | + * | Requester +-->+-->|xSNI Node +-- -- --+ xMNI Node +-->+--->| Completer | + * | | | | | | | | | | + * +------------+ | | PSAM/APU | | APU | | +-----------+ + * | +----------+ +-----------+ | + * | | + * | | + * | | + * | | + * | | + * +---------------------------------------+ + */ /*! * \brief NoC S3 node type enumerations. @@ -103,22 +124,106 @@ struct mod_noc_s3_dev { * receiving it. */ struct mod_noc_s3_platform_notification { - /*! Identifier of the notification id */ + /*! Identifier of the notification id. */ const fwk_id_t notification_id; - /*! Identifier of the module sending the notification */ + /*! Identifier of the module sending the notification. */ const fwk_id_t source_id; }; +/*! + * \brief Data to configure carveout in PSAM of an xSNI port. + */ +struct mod_noc_s3_psam_region { + /*! Base address of the carveout. */ + uint64_t base_address; + /*! Size of the carveout. */ + uint64_t size; + /*! Target xMNI node ID. */ + uint32_t target_id; +}; + +/*! + * \brief Component configuration info data. + */ +struct mod_noc_s3_comp_config { + /*! Type of the component. */ + enum mod_noc_s3_node_type type; + /*! ID of the component port. */ + uint32_t id; + /*! Info of carveouts to be mapped in PSAM. */ + struct mod_noc_s3_psam_region *psam_regions; + /*! Number of carveouts for PSAM. */ + uint32_t psam_region_count; +}; + /*! * \brief Info to configure ports in the NoC S3 block. */ struct mod_noc_s3_element_config { /*! NoC S3 periphbase address, same as CFGNI0 address. */ uintptr_t periphbase; - /*! Platform notification source and notification id */ + /*! List of component nodes to be configured. */ + struct mod_noc_s3_comp_config *component_config; + /*! Number of component nodes. */ + uint32_t component_count; + /*! Platform notification source and notification id. */ struct mod_noc_s3_platform_notification plat_notification; }; +/*! + * \brief NoC S3 module API indices. + */ +enum mod_noc_s3_api_idx { + /*! Interface to configure carveouts in a PSAM. */ + MOD_NOC_S3_API_SETUP_PSAM, + /*! Total API count. */ + MOD_NOC_S3_API_COUNT +}; + +/*! + * \brief Module interface to manage mappings. + */ +struct mod_noc_s3_memmap_api { + /*! + * \brief Program a region in the xSNI node of the target NoC S3. This API + * maps a region and returns the index where the region is mapped. + * + * \param[in] dev Device handler containing base address of + * the registers to configure NoC S3. + * \param[in] component_config xSNI configuration information for + * configuring a region into the target xSNI + * node's PSAM. + * \param[out] region_idx[out] Index of the mapped region. + * + * \return FWK_E_DATA if mapping region is invalid. + * \return FWK_E_PARAM if an invalid parameter was encountered. + * \return FWK_E_SUCESS if regions are mapped succesfully. + */ + int (*map_region_in_psam)( + struct mod_noc_s3_dev *dev, + struct mod_noc_s3_comp_config *component_config, + uint8_t *region_idx); + + /*! + * \brief Remove the mapping from the region in PSAM. + * + * \param[in] dev Device handler containing base address of + * the registers to configure NoC S3. + * \param[in] component_config xSNI configuration information for + * configuring a region into the target xSNI + * node's PSAM. + * \param[out] region_idx Index of the mapped region. + * + * \return FWK_E_DATA if mapping region is invalid. + * \return FWK_E_PARAM if an invalid parameter was encountered. + * \return FWK_E_SUCESS if regions are unmapped succesfully. + */ + int (*unmap_region_in_psam)( + struct mod_noc_s3_dev *dev, + struct mod_noc_s3_comp_config *component_config, + uint8_t region_idx); +}; + /*! * \} */ diff --git a/module/noc_s3/src/mod_noc_s3.c b/module/noc_s3/src/mod_noc_s3.c index 4fa71a3bc..2bde56ec8 100644 --- a/module/noc_s3/src/mod_noc_s3.c +++ b/module/noc_s3/src/mod_noc_s3.c @@ -5,6 +5,10 @@ * SPDX-License-Identifier: BSD-3-Clause * */ +#include +#include +#include + #include #include @@ -74,6 +78,7 @@ static int mod_noc_s3_element_init( static int mod_noc_s3_start(fwk_id_t id) { struct mod_noc_s3_element_config *config; + struct mod_noc_s3_dev *dev; unsigned int element_id; int status; @@ -87,6 +92,7 @@ static int mod_noc_s3_start(fwk_id_t id) element_id = fwk_id_get_element_idx(id); config = noc_s3_ctx.element_ctx[element_id].config; + dev = &noc_s3_ctx.element_ctx[element_id].noc_s3_dev; if (!fwk_id_is_equal(config->plat_notification.source_id, FWK_ID_NONE)) { status = fwk_notification_subscribe( config->plat_notification.notification_id, @@ -95,6 +101,11 @@ static int mod_noc_s3_start(fwk_id_t id) if (status != FWK_SUCCESS) { return status; } + } else { + status = program_static_mapped_regions(config, dev); + if (status != FWK_SUCCESS) { + return status; + } } return FWK_SUCCESS; @@ -105,12 +116,21 @@ static int mod_noc_s3_process_notification( struct fwk_event *resp_event) { struct mod_noc_s3_element_config *config; + struct mod_noc_s3_dev *dev; unsigned int element_id; int status; element_id = fwk_id_get_element_idx(event->target_id); config = noc_s3_ctx.element_ctx[element_id].config; + dev = &noc_s3_ctx.element_ctx[element_id].noc_s3_dev; if (fwk_id_is_equal(event->id, config->plat_notification.notification_id)) { + status = program_static_mapped_regions(config, dev); + if (status != FWK_SUCCESS) { + fwk_trap(); + return status; + } + + /* Element is initialized, unsubscribe from the notification. */ status = fwk_notification_unsubscribe( event->id, event->source_id, event->target_id); if (status != FWK_SUCCESS) { @@ -121,10 +141,39 @@ static int mod_noc_s3_process_notification( return FWK_SUCCESS; } +static struct mod_noc_s3_memmap_api noc_s3_memmap_api = { + .map_region_in_psam = map_region_in_psam, + .unmap_region_in_psam = unmap_region_in_psam, +}; + +static int mod_noc_s3_process_bind_request( + fwk_id_t requester_id, + fwk_id_t targer_id, + fwk_id_t api_id, + const void **api) +{ + enum mod_noc_s3_api_idx api_idx; + int status; + + api_idx = fwk_id_get_api_idx(api_id); + switch (api_idx) { + case MOD_NOC_S3_API_SETUP_PSAM: + *api = &noc_s3_memmap_api; + status = FWK_SUCCESS; + break; + default: + status = FWK_E_DATA; + }; + + return status; +} + const struct fwk_module module_noc_s3 = { + .api_count = MOD_NOC_S3_API_COUNT, .type = FWK_MODULE_TYPE_DRIVER, .init = mod_noc_s3_init, .element_init = mod_noc_s3_element_init, .start = mod_noc_s3_start, + .process_bind_request = mod_noc_s3_process_bind_request, .process_notification = mod_noc_s3_process_notification, }; diff --git a/module/noc_s3/src/noc_s3.c b/module/noc_s3/src/noc_s3.c index 924275e52..ef8f80896 100644 --- a/module/noc_s3/src/noc_s3.c +++ b/module/noc_s3/src/noc_s3.c @@ -58,3 +58,113 @@ bool noc_s3_is_node_type_component(enum mod_noc_s3_node_type node_type) return is_component; } + +/* Check if the subfeature type is correct. */ +bool noc_s3_is_type_subfeature(enum noc_s3_subfeature_type type) +{ + bool is_subfeature; + + switch (type) { + case NOC_S3_NODE_TYPE_APU: + case NOC_S3_NODE_TYPE_PSAM: + case NOC_S3_NODE_TYPE_FCU: + case NOC_S3_NODE_TYPE_IDM: + is_subfeature = true; + break; + default: + is_subfeature = false; + break; + }; + + return is_subfeature; +} + +/* + * Check if the incoming address range is overlapping with the mapped address. + */ +bool noc_s3_check_overlap( + uint64_t start_a, + uint64_t end_a, + uint64_t start_b, + uint64_t end_b) +{ + return ((start_a <= end_b) && (start_b <= end_a)); +} + +/* + * Compute the peripheral base address of a subfeature by taking the component + * offset from the discovery table, parsing the component to obtain the + * subfeature offset, and then adding the subfeature offset to the peripheral + * base. + */ +int noc_s3_get_subfeature_address( + struct mod_noc_s3_dev *dev, + enum mod_noc_s3_node_type node_type, + enum noc_s3_subfeature_type subfeature_type, + uint32_t node_id, + uintptr_t *subfeature_address) +{ + struct noc_s3_component_cfg_hdr *component_hdr; + struct noc_s3_discovery_data *discovery_data; + uint32_t component_offset; + uint32_t subfeature_offset; + int status; + + /* + * Find the target xSNI node and get register base address for system + * address map subfeature. + */ + discovery_data = &dev->discovery_data; + if (discovery_data == NULL) { + FWK_LOG_ERR(MOD_NAME "Invalid discovery table."); + return FWK_E_PARAM; + } + + /* Make sure that the node type is either domain or component. */ + if (!noc_s3_is_node_type_domain(node_type) && + !noc_s3_is_node_type_component(node_type)) { + return FWK_E_PARAM; + } + + if (!noc_s3_is_type_subfeature(subfeature_type)) { + return FWK_E_PARAM; + } + + /* + * Make sure that the id is not more than the maximum known id. Maximum + * known id is 1 less than maximum row size. + */ + if (node_id >= discovery_data->max_node_size[node_type]) { + FWK_LOG_ERR( + MOD_NAME "Node ID out of range. Input ID: %" PRId32 ", Max ID %d.", + node_id, + discovery_data->max_node_size[node_type]); + return FWK_E_PARAM; + } + + if (subfeature_address == NULL) { + return FWK_E_PARAM; + } + + /* Get the component offset from the discovery table. */ + component_offset = discovery_data->table[node_type][node_id]; + if (component_offset == 0) { + FWK_LOG_ERR( + MOD_NAME "Node(%d) ID:%" PRId32 " Invalid offset.", + node_type, + node_id); + return FWK_E_PARAM; + } + component_hdr = + (struct noc_s3_component_cfg_hdr *)(dev->periphbase + component_offset); + + /* Parse the component header and get offset of the subfeature. */ + status = noc_s3_get_subfeature_offset( + component_hdr, subfeature_type, &subfeature_offset); + if (status != FWK_SUCCESS) { + return status; + } + *subfeature_address = dev->periphbase + subfeature_offset; + + return FWK_SUCCESS; +} diff --git a/module/noc_s3/src/noc_s3.h b/module/noc_s3/src/noc_s3.h index f1cbb9571..f5108dae6 100644 --- a/module/noc_s3/src/noc_s3.h +++ b/module/noc_s3/src/noc_s3.h @@ -50,4 +50,63 @@ bool noc_s3_is_node_type_domain(enum mod_noc_s3_node_type node_type); */ bool noc_s3_is_node_type_component(enum mod_noc_s3_node_type node_type); +/*! + * \brief Check if the type is of type subfeature. + * + * \param[in] type Subfeature type. + * + * \return True if node is a subfeature. + * \return False if node is not a subfeature. + */ +bool noc_s3_is_type_subfeature(enum noc_s3_subfeature_type type); + +/*! + * \brief Check if the incoming address range is overlapping with the mapped + * address. + * + * \details Looking at the case below, if there is an overlap, then incoming + * address base will less than mapped end and mapped base will be less + * than incoming end. + * + * start|------Region A------|end + * start|----------Region B-----------|end + * + * \param start_a Region A start address. + * \param end_a Region A end address. + * \param start_b Region B start address. + * \param end_b Region B end address. + * + * \return true Overlapping regions. + * \return false Non overlapping region. + */ +bool noc_s3_check_overlap( + uint64_t start_a, + uint64_t end_a, + uint64_t start_b, + uint64_t end_b); + +/*! + * \brief Calculate the peripheral base address of the subfeature. + * + * \details This API validates the discovery table and the parameters and then + * calculate the base address of the target subfeature. The component + * offset is fetched from the discovery table and the subfeature offset + * is then taken from the component header. + * + * \param dev NoC S3 Device handler. + * \param node_type Target node type. + * \param subfeature_type Subfeature type of the component. + * \param node_id Node id of the component. + * \param subfeature_address computed subfeature address. + * + * \return FWK_E_PARAM If input arguments are invalid. + * \return FWK_SUCCESS If the subfeature address calculated successfully. + */ +int noc_s3_get_subfeature_address( + struct mod_noc_s3_dev *dev, + enum mod_noc_s3_node_type node_type, + enum noc_s3_subfeature_type subfeature_type, + uint32_t node_id, + uintptr_t *subfeature_address); + #endif /* NOC_S3_H */ diff --git a/module/noc_s3/src/noc_s3_psam.c b/module/noc_s3/src/noc_s3_psam.c new file mode 100644 index 000000000..733256ec2 --- /dev/null +++ b/module/noc_s3/src/noc_s3_psam.c @@ -0,0 +1,444 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include + +/* Global PSAM control. */ +#define NOC_S3_SAM_STATUS_SETUP_COMPLETE 0x1 + +/* Minimum page size if 4K. Address and size should be 4K aligned. */ +#define NOC_S3_ADDRESS_PAGE_SIZE (1ULL << 12) +#define NOC_S3_ADDRESS(addr) ((addr) & ~(NOC_S3_ADDRESS_PAGE_SIZE - 1)) + +/* Target xMNI ID configuration. */ +#define NOC_S3_TARGET_ID_SIZE (1ULL << 8) +#define NOC_S3_TARGET_ID(target_id) (target_id & (NOC_S3_TARGET_ID_SIZE - 1)) + +/* Check if the given address is 4K aligned. */ +#define NOC_S3_ADDRESS_VALID(addr) \ + (((addr) & (NOC_S3_ADDRESS_PAGE_SIZE - 1)) == 0) + +/* Helpers for enabling and disabling a region in PSAM. */ +#define NOC_S3_NH_REGION_VALID 1UL +#define NOC_S3_DISABLE_REGION(cfg) ((cfg) &= ~(NOC_S3_NH_REGION_VALID)) +#define NOC_S3_ENABLE_REGION(cfg) ((cfg) |= (NOC_S3_NH_REGION_VALID)) + +/* Non hash region count mask. */ +#define NUM_NH_REGION_MASK 0xFF +#define GET_NH_REGION_COUNT(reg) (reg & NUM_NH_REGION_MASK) + +/* Only xSNI support PSAM programming. */ +bool is_psam_supported(enum mod_noc_s3_node_type type) +{ + bool psam_supported; + + switch (type) { + case MOD_NOC_S3_NODE_TYPE_ASNI: + case MOD_NOC_S3_NODE_TYPE_HSNI: + psam_supported = true; + break; + default: + psam_supported = false; + break; + } + + return psam_supported; +} + +/* Check if the region is enabled by checking the 0 bit. */ +static bool region_enabled(uint64_t cfg1_cfg0) +{ + return (cfg1_cfg0 & NOC_S3_NH_REGION_VALID) == NOC_S3_NH_REGION_VALID; +} + +/* + * PSAM region de-initialization by setting the nh_region register to 0. + */ +static int psam_nhregion_deinit(struct noc_s3_psam_reg *reg, uint64_t region) +{ + uint8_t region_count; + + region_count = GET_NH_REGION_COUNT(reg->sam_unit_info); + if (region >= region_count) { + return FWK_E_RANGE; + } + + FWK_TRACE(MOD_NAME "Removing Region: %lld region at: %p", region, reg); + + /* Clear base address */ + reg->nh_region[region].cfg1_cfg0 = 0; + /* Clear end address */ + reg->nh_region[region].cfg3_cfg2 = 0; + __DMB(); + + return FWK_SUCCESS; +} + +/* + * Initialize a non hashed region in the PSAM. + */ +static int psam_nhregion_init( + struct noc_s3_psam_reg *reg, + uint64_t base_addr, + uint64_t end_addr, + uint64_t target_id, + uint64_t region) +{ + uint8_t region_count; + + region_count = GET_NH_REGION_COUNT(reg->sam_unit_info); + if (region >= region_count) { + return FWK_E_RANGE; + } + + if (!NOC_S3_ADDRESS_VALID(base_addr)) { + return FWK_E_PARAM; + } + + if (!NOC_S3_ADDRESS_VALID(end_addr + 1)) { + return FWK_E_PARAM; + } + + FWK_TRACE( + MOD_NAME "Programming Region: %" PRId64 " region at: %p", region, reg); + FWK_TRACE( + MOD_NAME "Address: Start: 0x%" PRIx64 ", End: 0x%" PRIx64 "", + base_addr, + end_addr); + FWK_TRACE(MOD_NAME "Target: 0x%" PRIx64 "", target_id); + + NOC_S3_DISABLE_REGION(reg->nh_region[region].cfg1_cfg0); + + __DMB(); + /* Set base address */ + reg->nh_region[region].cfg1_cfg0 = NOC_S3_ADDRESS(base_addr); + /* Set end address */ + reg->nh_region[region].cfg3_cfg2 = + NOC_S3_ADDRESS(end_addr) | NOC_S3_TARGET_ID(target_id); + __DMB(); + + NOC_S3_ENABLE_REGION(reg->nh_region[region].cfg1_cfg0); + + return FWK_SUCCESS; +} + +/* + * Return the number of the unmapped non hashed region. This also checks if the + * incoming region is overlapping with the mapped region. + */ +static int find_region_in_psam( + struct noc_s3_psam_reg *reg, + uint64_t base_addr, + uint64_t end_addr, + uint8_t *region) +{ + uint64_t mapped_start; + uint64_t mapped_end; + uint8_t region_count; + uint8_t count; + bool found; + + found = false; + region_count = GET_NH_REGION_COUNT(reg->sam_unit_info); + fwk_check(region_count != 0); + for (count = 0; count < region_count; count++) { + mapped_start = NOC_S3_ADDRESS(reg->nh_region[count].cfg1_cfg0); + mapped_end = NOC_S3_ADDRESS(reg->nh_region[count].cfg3_cfg2); + /* + * This makes sure that all the regions are checked for the overlap. + * Even if the region is found, this will loop over all of the regions + * to make sure that there is no overlap with the mapped regions. + */ + if (!region_enabled(reg->nh_region[count].cfg1_cfg0)) { + if (!found) { + found = true; + *region = count; + } + continue; + } + + if (noc_s3_check_overlap( + base_addr, end_addr, mapped_start, mapped_end)) { + FWK_LOG_ERR("Region overlap found"); + FWK_LOG_ERR( + "Incoming: Start: 0x%" PRIx64 ", End: 0x%" PRIx64 "", + base_addr, + end_addr); + FWK_LOG_ERR( + "Mapped: Start: 0x%" PRIx64 ", End: 0x%" PRIx64 "", + mapped_start, + mapped_end); + return FWK_E_PARAM; + } + } + + if (found) { + return FWK_SUCCESS; + } + + FWK_LOG_ERR("Out of PSAM empty regions"); + return FWK_E_RANGE; +} + +/* Global PSAM enable. */ +static void psam_enable(struct noc_s3_psam_reg *reg) +{ + reg->sam_status |= NOC_S3_SAM_STATUS_SETUP_COMPLETE; + __DMB(); +} + +/* Global PSAM disable. */ +static void psam_disable(struct noc_s3_psam_reg *reg) +{ + reg->sam_status &= ~NOC_S3_SAM_STATUS_SETUP_COMPLETE; + __DMB(); +} + +/* + * Extract the base offset from the discovery table of the target xSNI node and + * then parse the list of subfeatures it supports. If the target subfeature is + * found then its address is returned. + */ +static int get_psam_base( + struct mod_noc_s3_dev *dev, + struct noc_s3_psam_reg **psam_reg, + enum mod_noc_s3_node_type type, + uint32_t xsni_id) +{ + uintptr_t subfeature_base; + int status; + + if (!is_psam_supported(type)) { + FWK_LOG_ERR(MOD_NAME "PSAM not supported for Node(%d)", type); + return FWK_E_PARAM; + } + + status = noc_s3_get_subfeature_address( + dev, type, NOC_S3_NODE_TYPE_PSAM, xsni_id, &subfeature_base); + if (status == FWK_SUCCESS) { + *psam_reg = (struct noc_s3_psam_reg *)subfeature_base; + } + + return status; +} + +/* + * Program regions in the PSAM. This function is called for mapping static + * regions and assumes that the interconnect is being programmed for the first + * time and starts from region 0. + */ +static int program_psam_regions( + struct noc_s3_psam_reg *psam_reg, + struct mod_noc_s3_psam_region *psam_regions, + size_t count) +{ + size_t region_idx; + int status; + + /* Disable all PSAM regions for this node. */ + psam_disable(psam_reg); + + for (region_idx = 0; region_idx < count; region_idx++) { + status = psam_nhregion_init( + psam_reg, + psam_regions[region_idx].base_address, + (psam_regions[region_idx].base_address + + psam_regions[region_idx].size) - + 1, + psam_regions[region_idx].target_id, + region_idx); + if (status != FWK_SUCCESS) { + return status; + } + } + + /* Enable all the regions. */ + psam_enable(psam_reg); + + return FWK_SUCCESS; +} + +/* Maps the list of regions for each xSNI node passed in the element config. */ +int program_static_mapped_regions( + struct mod_noc_s3_element_config *config, + struct mod_noc_s3_dev *dev) +{ + struct mod_noc_s3_comp_config *component_config; + struct noc_s3_psam_reg *psam_base; + uint8_t idx; + int status; + + if ((config == NULL) || (dev == NULL)) { + return FWK_E_PARAM; + } + + /* Make sure that the discovery is completed and the table is populated. */ + if (!dev->discovery_completed) { + status = noc_s3_discovery(dev); + if (status != FWK_SUCCESS) { + FWK_LOG_ERR(MOD_NAME "NoC S3 Device discovery failed."); + return status; + } + } + + component_config = config->component_config; + /* Program all the xSNI ports. */ + for (idx = 0; idx < config->component_count; idx++) { + /* + * Find the target xSNI node and get register base address for system + * address map subfeature. + */ + status = get_psam_base( + dev, + &psam_base, + component_config[idx].type, + component_config[idx].id); + if (status != FWK_SUCCESS) { + return status; + } + + /* Program all the regions in the target xSNI port. */ + status = program_psam_regions( + psam_base, + component_config[idx].psam_regions, + component_config[idx].psam_region_count); + if (status != FWK_SUCCESS) { + return status; + } + } + + return FWK_SUCCESS; +} + +int map_region_in_psam( + struct mod_noc_s3_dev *dev, + struct mod_noc_s3_comp_config *component_config, + uint8_t *region) +{ + struct mod_noc_s3_psam_region *psam_region; + struct noc_s3_psam_reg *psam_base; + uint64_t base_address; + uint64_t end_address; + uint32_t target_id; + int status; + + if ((dev == NULL) || (dev->periphbase == 0)) { + return FWK_E_PARAM; + } + + if ((component_config == NULL) || + (component_config->psam_regions == NULL) || + (component_config->psam_region_count != 1)) { + return FWK_E_PARAM; + } + + /* Make sure that the discovery is completed and the table is populated. */ + if (!dev->discovery_completed) { + status = noc_s3_discovery(dev); + if (status != FWK_SUCCESS) { + FWK_LOG_ERR(MOD_NAME "NoC S3 Device discovery failed."); + return status; + } + } + + status = get_psam_base( + dev, &psam_base, component_config->type, component_config->id); + if (status != FWK_SUCCESS) { + return status; + } + + psam_region = component_config->psam_regions; + /* Program all the regions in the the target xSNI. */ + base_address = psam_region->base_address; + target_id = psam_region->target_id; + end_address = (psam_region->base_address + psam_region->size) - 1; + + /* + * Find and return the region number of an empty slot. + * If the region overlaps with another one then return error. + */ + status = find_region_in_psam(psam_base, base_address, end_address, region); + if (status != FWK_SUCCESS) { + return status; + } + + psam_disable(psam_base); + + /* Map the address in the target non hashed region. */ + status = psam_nhregion_init( + psam_base, base_address, end_address, target_id, *region); + if (status != FWK_SUCCESS) { + return status; + } + + psam_enable(psam_base); + + return FWK_SUCCESS; +} + +int unmap_region_in_psam( + struct mod_noc_s3_dev *dev, + struct mod_noc_s3_comp_config *component_config, + uint8_t region) +{ + struct noc_s3_psam_reg *psam_base; + int status; + + if ((dev == NULL) || (dev->periphbase == 0)) { + return FWK_E_PARAM; + } + + if (component_config == NULL) { + return FWK_E_PARAM; + } + + /* Make sure that the discovery is completed and the table is populated. */ + if (!dev->discovery_completed) { + status = noc_s3_discovery(dev); + if (status != FWK_SUCCESS) { + FWK_LOG_ERR(MOD_NAME "NoC S3 Device discovery failed."); + return status; + } + } + + /* + * Find the target xSNI node and get register base address for system + * address map subfeature. + */ + status = get_psam_base( + dev, &psam_base, component_config->type, component_config->id); + if (status != FWK_SUCCESS) { + return status; + } + + psam_disable(psam_base); + + /* Remove the target region from the PSAM mappings. */ + status = psam_nhregion_deinit(psam_base, region); + if (status != FWK_SUCCESS) { + return status; + } + + psam_enable(psam_base); + + return FWK_SUCCESS; +} diff --git a/module/noc_s3/src/noc_s3_psam.h b/module/noc_s3/src/noc_s3_psam.h new file mode 100644 index 000000000..b7c35f80c --- /dev/null +++ b/module/noc_s3/src/noc_s3_psam.h @@ -0,0 +1,77 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#ifndef NOC_S3_PSAM_H +#define NOC_S3_PSAM_H + +#include + +#include + +/*! + * \brief Map the list of statically mapped regions in the NoC S3. + * + * \details This function maps all the regions in the config in the non hash + * region. It assumes that the NoC S3 is being programmed for the first + * time and start from the region 0 and all the regions are non + * overlapping. + * + * \param[in] config Configuration to region memory map in the target NoC S3 NCI + * block. + * \param[in] dev Device handler. + * + * \return FWK_E_SUCCESS Regions mapped successfully. + * \return FWK_E_PARAM An invalid parameter was encountered. + * \return FWK_E_RANGE Out of regions. + */ +int program_static_mapped_regions( + struct mod_noc_s3_element_config *config, + struct mod_noc_s3_dev *dev); + +/*! + * \brief Map a region in the target xSNI node. + * + * \details This API maps a carveout in xSNI's PSAM. The function looks for the + * next immediate available region to map. It also updates the region + * number in the location passed by the caller. This function also + * checks for overlap between the carveouts. + * + * \param[in] dev Device handler containing base address of the + * registers to configure NoC S3. + * \param[in] comp_config Configuration info that contains the target and + * source node ids and carveout's base and size. + * \param[out] region[out] Index of the mapped PSAM region. + * + * \return FWK_E_SUCCESS Regions mapped successfully. + * \return FWK_E_PARAM An invalid parameter was encountered. + * \return FWK_E_RANGE Out of regions. + */ +int map_region_in_psam( + struct mod_noc_s3_dev *dev, + struct mod_noc_s3_comp_config *comp_config, + uint8_t *region); + +/*! + * \brief Disable and reset the register of the region in PSAM. + * + * \param[in] dev Device handler containing base address of the + * registers to configure NoC S3. + * \param[in] comp_config Configuration info that contains the target and source + * node ids and carveout's base and size. + * \param[in] region Index of the region + * + * \return FWK_E_SUCCESS Regions mapped successfully. + * \return FWK_E_PARAM An invalid parameter was encountered. + * \return FWK_E_RANGE Out of regions. + */ +int unmap_region_in_psam( + struct mod_noc_s3_dev *dev, + struct mod_noc_s3_comp_config *comp_config, + uint8_t region); + +#endif /* NOC_S3_PSAM_H */ diff --git a/module/noc_s3/src/noc_s3_reg.h b/module/noc_s3/src/noc_s3_reg.h index 4a774ad2a..2ab9c947e 100644 --- a/module/noc_s3/src/noc_s3_reg.h +++ b/module/noc_s3/src/noc_s3_reg.h @@ -156,6 +156,48 @@ static_assert( COMPONENT_ID3_OFFSET == offsetof(struct noc_s3_global_reg, component_id3)); +/*! Definitions for number of regions in the PSAM. */ +#define NOC_S3_MAX_NUM_REGIONS 128 +#define NOC_S3_MAX_NUM_HTG_REGIONS 32 +#define NOC_S3_MAX_NUM_HTG_TGID_NUM 32 +#define NOC_S3_MAX_NUM_TOP_ADDR_CFG 32 +#define NOC_S3_MAX_NUM_VD_POINTERS 1010 +#define NP2_TOP_ADDR_CFG_OFFSET 0xb80 +/*! Region configuration registers. */ +struct region_regs { + /* Base Address */ + FWK_RW uint64_t cfg1_cfg0; + /* End Address */ + FWK_RW uint64_t cfg3_cfg2; +}; + +/*! + * \brief NoC S3 PSAM register map + */ +struct noc_s3_psam_reg { + FWK_R uint32_t sam_unit_info; + FWK_RW uint32_t sam_status; + const uint32_t reserved_0[2]; + FWK_RW uint32_t htg_addr_mask_l; + FWK_RW uint32_t htg_addr_mask_u; + FWK_RW uint32_t axid_mask; + const uint32_t reserved_1; + FWK_RW uint32_t cmp_addr_mask_l; + FWK_RW uint32_t cmp_addr_mask_u; + const uint32_t reserved_2[2]; + FWK_RW uint32_t generic_config_reg0; + FWK_RW uint32_t generic_config_reg1; + const uint32_t reserved_3[50]; + struct region_regs nh_region[NOC_S3_MAX_NUM_REGIONS]; + struct region_regs htg_region[NOC_S3_MAX_NUM_HTG_REGIONS]; + FWK_RW uint32_t htg_tgtid_cfg[NOC_S3_MAX_NUM_HTG_TGID_NUM]; + FWK_RW uint32_t np2_top_addr_cfg[NOC_S3_MAX_NUM_TOP_ADDR_CFG]; +}; + +static_assert( + NP2_TOP_ADDR_CFG_OFFSET == + offsetof(struct noc_s3_psam_reg, np2_top_addr_cfg)); + // clang-format on #endif /* NOC_S3_REG_H */ -- GitLab