diff --git a/module/CMakeLists.txt b/module/CMakeLists.txt index 6a0384f893422512a24340ef4d642676d088a9d8..670f116b7c36a4724954108c56a3e1d3e13c6df8 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 0000000000000000000000000000000000000000..a9ae4d54754a9d5ae3c79c5864aebb88e9be5e45 --- /dev/null +++ b/module/noc_s3/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# 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" + 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_psam.c") diff --git a/module/noc_s3/Module.cmake b/module/noc_s3/Module.cmake new file mode 100644 index 0000000000000000000000000000000000000000..d7e54cd064be19811256597a14486b811036d84b --- /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/doc/noc_s3.md b/module/noc_s3/doc/noc_s3.md new file mode 100644 index 0000000000000000000000000000000000000000..3fc70d4a0d6af456a9cbf88d50848f30c42ca4bd --- /dev/null +++ b/module/noc_s3/doc/noc_s3.md @@ -0,0 +1,117 @@ +\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. + +# 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 new file mode 100644 index 0000000000000000000000000000000000000000..0baf80bac35dc9d8e088cf1307071326bfa6d79f --- /dev/null +++ b/module/noc_s3/include/mod_noc_s3.h @@ -0,0 +1,235 @@ +/* + * 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 + * \{ + */ +/* + * +---------------------------------------+ + * | 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. + */ +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. + * + * \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 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; + /*! 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); +}; + +/*! + * \} + */ + +/*! + * \} + */ + +#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 0000000000000000000000000000000000000000..2bde56ec8d8ced7d2a9713a2bea983bd05e554ed --- /dev/null +++ b/module/noc_s3/src/mod_noc_s3.c @@ -0,0 +1,179 @@ +/* + * 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 +#include + +#include + +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 { + /* 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; + noc_s3_ctx.element_ctx[idx].noc_s3_dev.periphbase = config->periphbase; + + return FWK_SUCCESS; +} + +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; + + 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; + 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, + config->plat_notification.source_id, + id); + if (status != FWK_SUCCESS) { + return status; + } + } else { + status = program_static_mapped_regions(config, dev); + 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; + 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) { + return status; + } + } + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..ef8f8089625c26137856a5041d7f7a270d303d48 --- /dev/null +++ b/module/noc_s3/src/noc_s3.c @@ -0,0 +1,170 @@ +/* + * 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; +} + +/* 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 new file mode 100644 index 0000000000000000000000000000000000000000..f5108dae6bd557928fd0424c52a5fce3323653bd --- /dev/null +++ b/module/noc_s3/src/noc_s3.h @@ -0,0 +1,112 @@ +/* + * 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); + +/*! + * \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_discovery.c b/module/noc_s3/src/noc_s3_discovery.c new file mode 100644 index 0000000000000000000000000000000000000000..0d94b11e6dcc09e4ec463c5857adb022b45219b2 --- /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 0000000000000000000000000000000000000000..5384844f0e08e85d97737204af79b3be216ff727 --- /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_psam.c b/module/noc_s3/src/noc_s3_psam.c new file mode 100644 index 0000000000000000000000000000000000000000..733256ec2c1bd7fded7baf132f479376a2c658ce --- /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 0000000000000000000000000000000000000000..b7c35f80ca310031b70787a3c45803f2510cde5c --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..2ab9c947e964486af12b3c577f432e4832ede96c --- /dev/null +++ b/module/noc_s3/src/noc_s3_reg.h @@ -0,0 +1,203 @@ +/* + * 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)); + +/*! 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 */