diff --git a/Makefile b/Makefile index 3aa0f9c9a..21de8625f 100644 --- a/Makefile +++ b/Makefile @@ -956,6 +956,7 @@ $(eval $(call assert_booleans,\ EL3_EXCEPTION_HANDLING \ ENABLE_AMU \ ENABLE_AMU_AUXILIARY_COUNTERS \ + ENABLE_AMU_FCONF \ AMU_RESTRICT_COUNTERS \ ENABLE_ASSERTIONS \ ENABLE_MPAM_FOR_LOWER_ELS \ @@ -1058,6 +1059,7 @@ $(eval $(call add_defines,\ DISABLE_MTPMU \ ENABLE_AMU \ ENABLE_AMU_AUXILIARY_COUNTERS \ + ENABLE_AMU_FCONF \ AMU_RESTRICT_COUNTERS \ ENABLE_ASSERTIONS \ ENABLE_BTI \ diff --git a/docs/components/activity-monitors.rst b/docs/components/activity-monitors.rst index 57d81f88b..ee280e287 100644 --- a/docs/components/activity-monitors.rst +++ b/docs/components/activity-monitors.rst @@ -10,6 +10,23 @@ When the ``ENABLE_AMU=1`` build option is provided, Trusted Firmware-A sets up the |AMU| prior to its exit from EL3, and will save and restore architected |AMU| counters as necessary upon suspend and resume. +Auxiliary counters +------------------ + +FEAT_AMUv1 describes a set of implementation-defined auxiliary counters (also +known as group 1 counters), controlled by the ``ENABLE_AMU_AUXILIARY_COUNTERS`` +build option. + +As a security precaution, Trusted Firmware-A does not enable these by default. +Instead, platforms may configure their auxiliary counters through one of two +possible mechanisms: + +- |FCONF|, controlled by the ``ENABLE_AMU_FCONF`` build option. +- A platform implementation of the ``plat_amu_topology`` function (the default). + +See :ref:`Activity Monitor Unit (AMU) Bindings` for documentation on the |FCONF| +device tree bindings. + -------------- *Copyright (c) 2021, Arm Limited. All rights reserved.* diff --git a/docs/components/fconf/amu-bindings.rst b/docs/components/fconf/amu-bindings.rst new file mode 100644 index 000000000..047f75ef8 --- /dev/null +++ b/docs/components/fconf/amu-bindings.rst @@ -0,0 +1,142 @@ +Activity Monitor Unit (AMU) Bindings +==================================== + +To support platform-defined Activity Monitor Unit (|AMU|) auxiliary counters +through FCONF, the ``HW_CONFIG`` device tree accepts several |AMU|-specific +nodes and properties. + +Bindings +^^^^^^^^ + +.. contents:: + :local: + +``/cpus/cpus/cpu*`` node properties +""""""""""""""""""""""""""""""""""" + +The ``cpu`` node has been augmented to support a handle to an associated |AMU| +view, which should describe the counters offered by the core. + ++---------------+-------+---------------+-------------------------------------+ +| Property name | Usage | Value type | Description | ++===============+=======+===============+=====================================+ +| ``amu`` | O | ```` | If present, indicates that an |AMU| | +| | | | is available and its counters are | +| | | | described by the node provided. | ++---------------+-------+---------------+-------------------------------------+ + +``/cpus/amus`` node properties +"""""""""""""""""""""""""""""" + +The ``amus`` node describes the |AMUs| implemented by the cores in the system. +This node does not have any properties. + +``/cpus/amus/amu*`` node properties +""""""""""""""""""""""""""""""""""" + +An ``amu`` node describes the layout and meaning of the auxiliary counter +registers of one or more |AMUs|, and may be shared by multiple cores. + ++--------------------+-------+------------+------------------------------------+ +| Property name | Usage | Value type | Description | ++====================+=======+============+====================================+ +| ``#address-cells`` | R | ```` | Value shall be 1. Specifies that | +| | | | the ``reg`` property array of | +| | | | children of this node uses a | +| | | | single cell. | ++--------------------+-------+------------+------------------------------------+ +| ``#size-cells`` | R | ```` | Value shall be 0. Specifies that | +| | | | no size is required in the ``reg`` | +| | | | property in children of this node. | ++--------------------+-------+------------+------------------------------------+ + +``/cpus/amus/amu*/counter*`` node properties +"""""""""""""""""""""""""""""""""""""""""""" + +A ``counter`` node describes an auxiliary counter belonging to the parent |AMU| +view. + ++-------------------+-------+-------------+------------------------------------+ +| Property name | Usage | Value type | Description | ++===================+=======+=============+====================================+ +| ``reg`` | R | array | Represents the counter register | +| | | | index, and must be a single cell. | ++-------------------+-------+-------------+------------------------------------+ +| ``enable-at-el3`` | O | ```` | The presence of this property | +| | | | indicates that this counter should | +| | | | be enabled prior to EL3 exit. | ++-------------------+-------+-------------+------------------------------------+ + +Example +^^^^^^^ + +An example system offering four cores made up of two clusters, where the cores +of each cluster share different |AMUs|, may use something like the following: + +.. code-block:: + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + amus { + amu0: amu-0 { + #address-cells = <1>; + #size-cells = <0>; + + counterX: counter@0 { + reg = <0>; + + enable-at-el3; + }; + + counterY: counter@1 { + reg = <1>; + + enable-at-el3; + }; + }; + + amu1: amu-1 { + #address-cells = <1>; + #size-cells = <0>; + + counterZ: counter@0 { + reg = <0>; + + enable-at-el3; + }; + }; + }; + + cpu0@00000 { + ... + + amu = <&amu0>; + }; + + cpu1@00100 { + ... + + amu = <&amu0>; + }; + + cpu2@10000 { + ... + + amu = <&amu1>; + }; + + cpu3@10100 { + ... + + amu = <&amu1>; + }; + } + +In this situation, ``cpu0`` and ``cpu1`` (the two cores in the first cluster), +share the view of their AMUs defined by ``amu0``. Likewise, ``cpu2`` and +``cpu3`` (the two cores in the second cluster), share the view of their |AMUs| +defined by ``amu1``. This will cause ``counterX`` and ``counterY`` to be enabled +for both ``cpu0`` and ``cpu1``, and ``counterZ`` to be enabled for both ``cpu2`` +and ``cpu3``. diff --git a/docs/components/fconf/index.rst b/docs/components/fconf/index.rst index 902063356..00a4e297b 100644 --- a/docs/components/fconf/index.rst +++ b/docs/components/fconf/index.rst @@ -145,3 +145,4 @@ Properties binding information :maxdepth: 1 fconf_properties + amu-bindings diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst index f9999e108..8a0167b6d 100644 --- a/docs/getting_started/build-options.rst +++ b/docs/getting_started/build-options.rst @@ -224,6 +224,10 @@ Common build options (also known as group 1 counters). These are implementation-defined counters, and as such require additional platform configuration. Default is 0. +- ``ENABLE_AMU_FCONF``: Enables configuration of the AMU through FCONF, which + allows platforms with auxiliary counters to describe them via the + ``HW_CONFIG`` device tree blob. Default is 0. + - ``ENABLE_ASSERTIONS``: This option controls whether or not calls to ``assert()`` are compiled out. For debug builds, this option defaults to 1, and calls to ``assert()`` are left in place. For release builds, this option defaults to 0 diff --git a/include/lib/extensions/amu.h b/include/lib/extensions/amu.h index 692d1adaf..6452f7e48 100644 --- a/include/lib/extensions/amu.h +++ b/include/lib/extensions/amu.h @@ -8,12 +8,40 @@ #define AMU_H #include +#include + #include +#include + #if __aarch64__ void amu_enable(bool el2_unused, cpu_context_t *ctx); #else void amu_enable(bool el2_unused); #endif +#if ENABLE_AMU_AUXILIARY_COUNTERS +/* + * AMU data for a single core. + */ +struct amu_core { + uint16_t enable; /* Mask of auxiliary counters to enable */ +}; + +/* + * Topological platform data specific to the AMU. + */ +struct amu_topology { + struct amu_core cores[PLATFORM_CORE_COUNT]; /* Per-core data */ +}; + +#if !ENABLE_AMU_FCONF +/* + * Retrieve the platform's AMU topology. A `NULL` return value is treated as a + * non-fatal error, in which case no auxiliary counters will be enabled. + */ +const struct amu_topology *plat_amu_topology(void); +#endif /* ENABLE_AMU_FCONF */ +#endif /* ENABLE_AMU_AUXILIARY_COUNTERS */ + #endif /* AMU_H */ diff --git a/include/lib/fconf/fconf_amu_getter.h b/include/lib/fconf/fconf_amu_getter.h new file mode 100644 index 000000000..2faee73b4 --- /dev/null +++ b/include/lib/fconf/fconf_amu_getter.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef FCONF_AMU_GETTER_H +#define FCONF_AMU_GETTER_H + +#include + +#define amu__config_getter(id) fconf_amu_config.id + +struct fconf_amu_config { + const struct amu_topology *topology; +}; + +extern struct fconf_amu_config fconf_amu_config; + +#endif /* FCONF_AMU_GETTER_H */ diff --git a/lib/extensions/amu/aarch32/amu.c b/lib/extensions/amu/aarch32/amu.c index e92b9f130..57b115825 100644 --- a/lib/extensions/amu/aarch32/amu.c +++ b/lib/extensions/amu/aarch32/amu.c @@ -11,6 +11,7 @@ #include "../amu_private.h" #include #include +#include #include #include @@ -181,6 +182,30 @@ void amu_enable(bool el2_unused) assert(amcgcr_cg0nc <= AMU_AMCGCR_CG0NC_MAX); + /* + * The platform may opt to enable specific auxiliary counters. This can + * be done via the common FCONF getter, or via the platform-implemented + * function. + */ + +#if ENABLE_AMU_AUXILIARY_COUNTERS + const struct amu_topology *topology; + +#if ENABLE_AMU_FCONF + topology = FCONF_GET_PROPERTY(amu, config, topology); +#else + topology = plat_amu_topology(); +#endif /* ENABLE_AMU_FCONF */ + + if (topology != NULL) { + unsigned int core_pos = plat_my_core_pos(); + + amcntenset1_el0_px = topology->cores[core_pos].enable; + } else { + ERROR("AMU: failed to generate AMU topology\n"); + } +#endif /* ENABLE_AMU_AUXILIARY_COUNTERS */ + /* * Enable the requested counters. */ @@ -190,6 +215,10 @@ void amu_enable(bool el2_unused) amcfgr_ncg = read_amcfgr_ncg(); if (amcfgr_ncg > 0U) { write_amcntenset1_px(amcntenset1_px); + +#if !ENABLE_AMU_AUXILIARY_COUNTERS + VERBOSE("AMU: auxiliary counters detected but support is disabled\n"); +#endif } /* Initialize FEAT_AMUv1p1 features if present. */ diff --git a/lib/extensions/amu/aarch64/amu.c b/lib/extensions/amu/aarch64/amu.c index 58094ae40..f7125204a 100644 --- a/lib/extensions/amu/aarch64/amu.c +++ b/lib/extensions/amu/aarch64/amu.c @@ -12,11 +12,17 @@ #include #include #include +#include #include #include #include +#if ENABLE_AMU_FCONF +# include +# include +#endif + struct amu_ctx { uint64_t group0_cnts[AMU_GROUP0_MAX_COUNTERS]; #if ENABLE_AMU_AUXILIARY_COUNTERS @@ -226,6 +232,30 @@ void amu_enable(bool el2_unused, cpu_context_t *ctx) assert(amcgcr_el0_cg0nc <= AMU_AMCGCR_CG0NC_MAX); + /* + * The platform may opt to enable specific auxiliary counters. This can + * be done via the common FCONF getter, or via the platform-implemented + * function. + */ + +#if ENABLE_AMU_AUXILIARY_COUNTERS + const struct amu_topology *topology; + +#if ENABLE_AMU_FCONF + topology = FCONF_GET_PROPERTY(amu, config, topology); +#else + topology = plat_amu_topology(); +#endif /* ENABLE_AMU_FCONF */ + + if (topology != NULL) { + unsigned int core_pos = plat_my_core_pos(); + + amcntenset1_el0_px = topology->cores[core_pos].enable; + } else { + ERROR("AMU: failed to generate AMU topology\n"); + } +#endif /* ENABLE_AMU_AUXILIARY_COUNTERS */ + /* * Enable the requested counters. */ @@ -235,6 +265,10 @@ void amu_enable(bool el2_unused, cpu_context_t *ctx) amcfgr_el0_ncg = read_amcfgr_el0_ncg(); if (amcfgr_el0_ncg > 0U) { write_amcntenset1_el0_px(amcntenset1_el0_px); + +#if !ENABLE_AMU_AUXILIARY_COUNTERS + VERBOSE("AMU: auxiliary counters detected but support is disabled\n"); +#endif } /* Initialize FEAT_AMUv1p1 features if present. */ diff --git a/lib/extensions/amu/amu.mk b/lib/extensions/amu/amu.mk index b4e04dd47..0d203cb1f 100644 --- a/lib/extensions/amu/amu.mk +++ b/lib/extensions/amu/amu.mk @@ -4,5 +4,21 @@ # SPDX-License-Identifier: BSD-3-Clause # +include lib/fconf/fconf.mk + AMU_SOURCES := lib/extensions/amu/${ARCH}/amu.c \ lib/extensions/amu/${ARCH}/amu_helpers.S + +ifneq (${ENABLE_AMU_AUXILIARY_COUNTERS},0) + ifeq (${ENABLE_AMU},0) + $(error AMU auxiliary counter support (`ENABLE_AMU_AUXILIARY_COUNTERS`) requires AMU support (`ENABLE_AMU`)) + endif +endif + +ifneq (${ENABLE_AMU_FCONF},0) + ifeq (${ENABLE_AMU_AUXILIARY_COUNTERS},0) + $(error AMU FCONF support (`ENABLE_AMU_FCONF`) is not necessary when auxiliary counter support (`ENABLE_AMU_AUXILIARY_COUNTERS`) is disabled) + endif + + AMU_SOURCES += ${FCONF_AMU_SOURCES} +endif diff --git a/lib/extensions/amu/amu_fconf.c b/lib/extensions/amu/amu_fconf.c new file mode 100644 index 000000000..c7fb80362 --- /dev/null +++ b/lib/extensions/amu/amu_fconf.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2021, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "amu_private.h" +#include +#include +#include +#include +#include + +#include + +static bool amu_topology_populated_ ; /* Whether the topology is valid */ +static struct amu_fconf_topology amu_topology_; /* Populated topology cache */ + +const struct amu_fconf_topology *amu_topology(void) +{ + if (!amu_topology_populated_) { + return NULL; + } + + return &amu_topology_; +} + +/* + * Populate the core-specific AMU structure with information retrieved from a + * device tree. + * + * Returns `0` on success, or a negative integer representing an error code. + */ +static int amu_fconf_populate_cpu_amu(const void *fdt, int parent, + struct amu_fconf_core *amu) +{ + int ret = 0; + int node = 0; + + fdt_for_each_subnode(node, fdt, parent) { + const char *name; + const char *value; + int len; + + uintptr_t idx = 0U; + + name = fdt_get_name(fdt, node, &len); + if (strncmp(name, "counter@", 8) != 0) { + continue; + } + + ret = fdt_get_reg_props_by_index(fdt, node, 0, &idx, NULL); + if (ret < 0) { + break; + } + + value = fdt_getprop(fdt, node, "enable-at-el3", &len); + if ((value == NULL) && (len != -FDT_ERR_NOTFOUND)) { + break; + } + + if (len != -FDT_ERR_NOTFOUND) { + amu->enable |= (1 << idx); + } + } + + if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { + return node; + } + + return ret; +} + +/* + * Within a `cpu` node, attempt to dereference the `amu` property, and populate + * the AMU information for the core. + * + * Returns `0` on success, or a negative integer representing an error code. + */ +static int amu_fconf_populate_cpu(const void *fdt, int node, uintptr_t mpidr) +{ + int ret; + int idx; + + uint32_t amu_phandle; + struct amu_fconf_core *amu; + + ret = fdt_read_uint32(fdt, node, "amu", &amu_phandle); + if (ret < 0) { + if (ret == -FDT_ERR_NOTFOUND) { + ret = 0; + } + + return ret; + } + + node = fdt_node_offset_by_phandle(fdt, amu_phandle); + if (node < 0) { + return node; + } + + idx = plat_core_pos_by_mpidr(mpidr); + amu = &amu_topology_.cores[idx]; + + return amu_fconf_populate_cpu_amu(fdt, node, amu); +} + +/* + * For every CPU node (`/cpus/cpu@n`) in an FDT, executes a callback passing a + * pointer to the FDT and the offset of the CPU node. If the return value of the + * callback is negative, it is treated as an error and the loop is aborted. In + * this situation, the value of the callback is returned from the function. + * + * Returns `0` on success, or a negative integer representing an error code. + */ +static int amu_fconf_foreach_cpu(const void *fdt, + int (*callback)(const void *, int, uintptr_t)) +{ + int ret = 0; + int parent, node = 0; + + parent = fdt_path_offset(fdt, "/cpus"); + if (parent < 0) { + if (parent == -FDT_ERR_NOTFOUND) { + parent = 0; + } + + return parent; + } + + fdt_for_each_subnode(node, fdt, parent) { + const char *name; + int len; + + uintptr_t mpidr = 0U; + + name = fdt_get_name(fdt, node, &len); + if (strncmp(name, "cpu@", 4) != 0) { + continue; + } + + ret = fdt_get_reg_props_by_index(fdt, node, 0, &mpidr, NULL); + if (ret < 0) { + break; + } + + ret = callback(fdt, node, mpidr); + if (ret < 0) { + break; + } + } + + if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { + return node; + } + + return ret; +} + +/* + * Populates the global `amu_topology` structure based on what's described by + * the hardware configuration device tree blob. + * + * The device tree is expected to provide an `amu` property for each `cpu` node, + * like so: + * + * cpu@0 { + * amu = <&cpu0_amu>; + * }; + * + * amus { + * cpu0_amu: amu-0 { + * counters { + * #address-cells = <2>; + * #size-cells = <0>; + * + * counter@x,y { + * reg = ; // Group x, counter y + * }; + * }; + * }; + * }; + */ +static int amu_fconf_populate(uintptr_t config) +{ + int ret = amu_fconf_foreach_cpu( + (const void *)config, amu_fconf_populate_cpu); + if (ret < 0) { + ERROR("AMU-FCONF: Failed to configure AMU: %d\n", ret); + } else { + amu_topology_populated_ = true; + } + + return ret; +} + +FCONF_REGISTER_POPULATOR(HW_CONFIG, amu, amu_fconf_populate); diff --git a/lib/fconf/fconf.mk b/lib/fconf/fconf.mk index bc6f60840..18bcb3590 100644 --- a/lib/fconf/fconf.mk +++ b/lib/fconf/fconf.mk @@ -11,3 +11,6 @@ FCONF_SOURCES += ${FDT_WRAPPERS_SOURCES} FCONF_DYN_SOURCES := lib/fconf/fconf_dyn_cfg_getter.c FCONF_DYN_SOURCES += ${FDT_WRAPPERS_SOURCES} + +FCONF_AMU_SOURCES := lib/fconf/fconf_amu_getter.c +FCONF_AMU_SOURCES += ${FDT_WRAPPERS_SOURCES} diff --git a/lib/fconf/fconf_amu_getter.c b/lib/fconf/fconf_amu_getter.c new file mode 100644 index 000000000..eff309cf9 --- /dev/null +++ b/lib/fconf/fconf_amu_getter.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2021, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +struct fconf_amu_config fconf_amu_config; +static struct amu_topology fconf_amu_topology_; + +/* + * Populate the core-specific AMU structure with information retrieved from a + * device tree. + * + * Returns `0` on success, or a negative integer representing an error code. + */ +static int fconf_populate_amu_cpu_amu(const void *fdt, int parent, + struct amu_core *amu) +{ + int ret = 0; + int node = 0; + + fdt_for_each_subnode(node, fdt, parent) { + const char *name; + const char *value; + int len; + + uintptr_t idx = 0U; + + name = fdt_get_name(fdt, node, &len); + if (strncmp(name, "counter@", 8) != 0) { + continue; + } + + ret = fdt_get_reg_props_by_index(fdt, node, 0, &idx, NULL); + if (ret < 0) { + break; + } + + value = fdt_getprop(fdt, node, "enable-at-el3", &len); + if ((value == NULL) && (len != -FDT_ERR_NOTFOUND)) { + break; + } + + if (len != -FDT_ERR_NOTFOUND) { + amu->enable |= (1 << idx); + } + } + + if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { + return node; + } + + return ret; +} + +/* + * Within a `cpu` node, attempt to dereference the `amu` property, and populate + * the AMU information for the core. + * + * Returns `0` on success, or a negative integer representing an error code. + */ +static int fconf_populate_amu_cpu(const void *fdt, int node, uintptr_t mpidr) +{ + int ret; + int idx; + + uint32_t amu_phandle; + struct amu_core *amu; + + ret = fdt_read_uint32(fdt, node, "amu", &amu_phandle); + if (ret < 0) { + if (ret == -FDT_ERR_NOTFOUND) { + ret = 0; + } + + return ret; + } + + node = fdt_node_offset_by_phandle(fdt, amu_phandle); + if (node < 0) { + return node; + } + + idx = plat_core_pos_by_mpidr(mpidr); + if (idx < 0) { + return -FDT_ERR_BADVALUE; + } + + amu = &fconf_amu_topology_.cores[idx]; + + return fconf_populate_amu_cpu_amu(fdt, node, amu); +} + +/* + * Populates the global `amu_topology` structure based on what's described by + * the hardware configuration device tree blob. + * + * The device tree is expected to provide an `amu` property for each `cpu` node, + * like so: + * + * cpu@0 { + * amu = <&cpu0_amu>; + * }; + * + * amus { + * cpu0_amu: amu-0 { + * counters { + * #address-cells = <2>; + * #size-cells = <0>; + * + * counter@x,y { + * reg = ; // Group x, counter y + * }; + * }; + * }; + * }; + */ +static int fconf_populate_amu(uintptr_t config) +{ + int ret = fdtw_for_each_cpu( + (const void *)config, fconf_populate_amu_cpu); + if (ret == 0) { + fconf_amu_config.topology = &fconf_amu_topology_; + } else { + ERROR("FCONF: failed to parse AMU information: %d\n", ret); + } + + return ret; +} + +FCONF_REGISTER_POPULATOR(HW_CONFIG, amu, fconf_populate_amu); diff --git a/make_helpers/defaults.mk b/make_helpers/defaults.mk index 4344cf8ba..8705d9247 100644 --- a/make_helpers/defaults.mk +++ b/make_helpers/defaults.mk @@ -307,6 +307,7 @@ CTX_INCLUDE_MTE_REGS := 0 ENABLE_AMU := 0 ENABLE_AMU_AUXILIARY_COUNTERS := 0 +ENABLE_AMU_FCONF := 0 AMU_RESTRICT_COUNTERS := 0 # By default, enable Scalable Vector Extension if implemented only for Non-secure