Merge "fix(measured-boot): don't strip last non-0 char" into integration
diff --git a/.versionrc.js b/.versionrc.js
index 9e54c7b..4e9c71f 100644
--- a/.versionrc.js
+++ b/.versionrc.js
@@ -94,7 +94,6 @@
 
                     return contents.replace(/^(version\s=\s")((\d).?)*$/m, _ver)
                 }
-
             },
         },
         {
diff --git a/Makefile b/Makefile
index 98f47a7..3dbf28e 100644
--- a/Makefile
+++ b/Makefile
@@ -710,16 +710,23 @@
 	BL32_LDFLAGS	+=	$(PIE_LDFLAGS)
 endif
 
-ifeq (${ARCH},aarch64)
+BL1_CPPFLAGS  += -DREPORT_ERRATA=${DEBUG}
+BL31_CPPFLAGS += -DREPORT_ERRATA=${DEBUG}
+BL32_CPPFLAGS += -DREPORT_ERRATA=${DEBUG}
+
 BL1_CPPFLAGS += -DIMAGE_AT_EL3
 ifeq ($(RESET_TO_BL2),1)
 BL2_CPPFLAGS += -DIMAGE_AT_EL3
 else
 BL2_CPPFLAGS += -DIMAGE_AT_EL1
 endif
+
+ifeq (${ARCH},aarch64)
 BL2U_CPPFLAGS += -DIMAGE_AT_EL1
 BL31_CPPFLAGS += -DIMAGE_AT_EL3
 BL32_CPPFLAGS += -DIMAGE_AT_EL1
+else
+BL32_CPPFLAGS += -DIMAGE_AT_EL3
 endif
 
 # Include the CPU specific operations makefile, which provides default
@@ -1074,11 +1081,6 @@
 # Variable for use with Python
 PYTHON			?=	python3
 
-# Variables for use with PRINT_MEMORY_MAP
-PRINT_MEMORY_MAP_PATH		?=	tools/memory
-PRINT_MEMORY_MAP		?=	${PRINT_MEMORY_MAP_PATH}/print_memory_map.py
-INVERTED_MEMMAP			?=	0
-
 # Variables for use with documentation build using Sphinx tool
 DOCS_PATH		?=	docs
 
@@ -1139,7 +1141,6 @@
         GICV2_G0_FOR_EL3 \
         HANDLE_EA_EL3_FIRST_NS \
         HW_ASSISTED_COHERENCY \
-        INVERTED_MEMMAP \
         MEASURED_BOOT \
         DRTM_SUPPORT \
         NS_TIMER_SWITCH \
@@ -1653,9 +1654,14 @@
 romlib.bin: libraries FORCE
 	${Q}${MAKE} PLAT_DIR=${PLAT_DIR} BUILD_PLAT=${BUILD_PLAT} ENABLE_BTI=${ENABLE_BTI} ARM_ARCH_MINOR=${ARM_ARCH_MINOR} INCLUDES='${INCLUDES}' DEFINES='${DEFINES}' --no-print-directory -C ${ROMLIBPATH} all
 
-# Call print_memory_map tool
 memmap: all
-	${Q}${PYTHON} ${PRINT_MEMORY_MAP} ${BUILD_PLAT} ${INVERTED_MEMMAP}
+ifdef UNIX_MK
+	${Q}PYTHONPATH=${CURDIR}/tools/memory \
+		${PYTHON} -m memory.memmap -sr ${BUILD_PLAT}
+else
+	${Q}set PYTHONPATH=${CURDIR}/tools/memory && \
+		${PYTHON} -m memory.memmap -sr ${BUILD_PLAT}
+endif
 
 doc:
 	@echo "  BUILD DOCUMENTATION"
diff --git a/bl1/bl1.ld.S b/bl1/bl1.ld.S
index bec234b..a2527e6 100644
--- a/bl1/bl1.ld.S
+++ b/bl1/bl1.ld.S
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2023, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2013-2023, Arm Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -24,6 +24,11 @@
 }
 
 SECTIONS {
+    ROM_REGION_START = ORIGIN(ROM);
+    ROM_REGION_LENGTH = LENGTH(ROM);
+    RAM_REGION_START = ORIGIN(RAM);
+    RAM_REGION_LENGTH = LENGTH(RAM);
+
     . = BL1_RO_BASE;
 
     ASSERT(. == ALIGN(PAGE_SIZE),
@@ -97,6 +102,7 @@
     ASSERT(__CPU_OPS_END__ > __CPU_OPS_START__,
         "cpu_ops not defined for this platform.")
 
+    ROM_REGION_END = .;
     . = BL1_RW_BASE;
 
     ASSERT(BL1_RW_BASE == ALIGN(PAGE_SIZE),
@@ -157,4 +163,5 @@
 #endif /* USE_COHERENT_MEM */
 
     ASSERT(. <= BL1_RW_LIMIT, "BL1's RW section has exceeded its limit.")
+    RAM_REGION_END = .;
 }
diff --git a/bl1/bl1_main.c b/bl1/bl1_main.c
index 7399bc8..3f64e27 100644
--- a/bl1/bl1_main.c
+++ b/bl1/bl1_main.c
@@ -17,7 +17,7 @@
 #include <drivers/auth/auth_mod.h>
 #include <drivers/auth/crypto_mod.h>
 #include <drivers/console.h>
-#include <lib/cpus/errata_report.h>
+#include <lib/cpus/errata.h>
 #include <lib/utils.h>
 #include <plat/common/platform.h>
 #include <smccc_helpers.h>
diff --git a/bl2/bl2.ld.S b/bl2/bl2.ld.S
index 458a12b..5f689d5 100644
--- a/bl2/bl2.ld.S
+++ b/bl2/bl2.ld.S
@@ -16,6 +16,8 @@
 }
 
 SECTIONS {
+    RAM_REGION_START = ORIGIN(RAM);
+    RAM_REGION_LENGTH = LENGTH(RAM);
     . = BL2_BASE;
 
     ASSERT(. == ALIGN(PAGE_SIZE),
@@ -116,6 +118,7 @@
 
     __RW_END__ = .;
     __BL2_END__ = .;
+    RAM_REGION_END = .;
 
     __BSS_SIZE__ = SIZEOF(.bss);
 
diff --git a/bl2/bl2.mk b/bl2/bl2.mk
index 41bcd12..19b955f 100644
--- a/bl2/bl2.mk
+++ b/bl2/bl2.mk
@@ -41,8 +41,7 @@
 BL2_SOURCES		+=	bl2/${ARCH}/bl2_el3_entrypoint.S	\
 				bl2/${ARCH}/bl2_el3_exceptions.S	\
 				bl2/${ARCH}/bl2_run_next_image.S        \
-				lib/cpus/${ARCH}/cpu_helpers.S		\
-				lib/cpus/errata_report.c
+				lib/cpus/${ARCH}/cpu_helpers.S
 
 ifeq (${DISABLE_MTPMU},1)
 BL2_SOURCES		+=	lib/extensions/mtpmu/${ARCH}/mtpmu.S
diff --git a/bl2/bl2_el3.ld.S b/bl2/bl2_el3.ld.S
index aa457fa..5da631c 100644
--- a/bl2/bl2_el3.ld.S
+++ b/bl2/bl2_el3.ld.S
@@ -31,7 +31,12 @@
 #endif /* !BL2_IN_XIP_MEM */
 
 SECTIONS {
+    RAM_REGION_START = ORIGIN(RAM);
+    RAM_REGION_LENGTH = LENGTH(RAM);
 #if BL2_IN_XIP_MEM
+    ROM_REGION_START = ORIGIN(ROM);
+    ROM_REGION_LENGTH = LENGTH(ROM);
+
     . = BL2_RO_BASE;
 
     ASSERT(. == ALIGN(PAGE_SIZE),
@@ -43,6 +48,11 @@
         "BL2_BASE address is not aligned on a page boundary.")
 #endif /* BL2_IN_XIP_MEM */
 
+#if SEPARATE_BL2_NOLOAD_REGION
+    RAM_NOLOAD_REGION_START = ORIGIN(RAM_NOLOAD);
+    RAM_NOLOAD_REGION_LENGTH = LENGTH(RAM_NOLOAD);
+#endif
+
 #if SEPARATE_CODE_AND_RODATA
     .text . : {
         __TEXT_START__ = .;
@@ -109,6 +119,7 @@
         "cpu_ops not defined for this platform.")
 
 #if BL2_IN_XIP_MEM
+    ROM_REGION_END = .;
     . = BL2_RW_BASE;
 
     ASSERT(BL2_RW_BASE == ALIGN(PAGE_SIZE),
@@ -138,6 +149,7 @@
 
 #if SEPARATE_BL2_NOLOAD_REGION
     __BL2_NOLOAD_END__ = .;
+    RAM_NOLOAD_REGION_END = .;
 
     . = SAVED_ADDR;
 #endif /* SEPARATE_BL2_NOLOAD_REGION */
@@ -198,6 +210,7 @@
         __COHERENT_RAM_END_UNALIGNED__ - __COHERENT_RAM_START__;
 #endif /* USE_COHERENT_MEM */
 
+    RAM_REGION_END = .;
 #if BL2_IN_XIP_MEM
     ASSERT(. <= BL2_RW_LIMIT, "BL2's RW content has exceeded its limit.")
 #else /* BL2_IN_XIP_MEM */
diff --git a/bl2u/bl2u.ld.S b/bl2u/bl2u.ld.S
index 52a925b..21c91b4 100644
--- a/bl2u/bl2u.ld.S
+++ b/bl2u/bl2u.ld.S
@@ -18,6 +18,8 @@
 }
 
 SECTIONS {
+    RAM_REGION_START = ORIGIN(RAM);
+    RAM_REGION_LENGTH = LENGTH(RAM);
     . = BL2U_BASE;
 
     ASSERT(. == ALIGN(PAGE_SIZE),
@@ -115,4 +117,5 @@
     __BSS_SIZE__ = SIZEOF(.bss);
 
     ASSERT(. <= BL2U_LIMIT, "BL2U image has exceeded its limit.")
+    RAM_REGION_END = .;
 }
diff --git a/bl31/bl31.ld.S b/bl31/bl31.ld.S
index 5ac83fa..abcae0c 100644
--- a/bl31/bl31.ld.S
+++ b/bl31/bl31.ld.S
@@ -26,6 +26,8 @@
 #endif /* PLAT_EXTRA_LD_SCRIPT */
 
 SECTIONS {
+    RAM_REGION_START = ORIGIN(RAM);
+    RAM_REGION_LENGTH = LENGTH(RAM);
     . = BL31_BASE;
 
     ASSERT(. == ALIGN(PAGE_SIZE),
@@ -198,6 +200,7 @@
 
     ASSERT(. <= BL31_LIMIT, "BL31 image has exceeded its limit.")
 #endif /* SEPARATE_NOBITS_REGION */
+    RAM_REGION_END = .;
 
     /DISCARD/ : {
         *(.dynsym .dynstr .hash .gnu.hash)
diff --git a/bl32/sp_min/sp_min.ld.S b/bl32/sp_min/sp_min.ld.S
index 1695e1e..0a2bad0 100644
--- a/bl32/sp_min/sp_min.ld.S
+++ b/bl32/sp_min/sp_min.ld.S
@@ -20,6 +20,8 @@
 #endif /* PLAT_SP_MIN_EXTRA_LD_SCRIPT */
 
 SECTIONS {
+    RAM_REGION_START = ORIGIN(RAM);
+    RAM_REGION_LENGTH = LENGTH(RAM);
     . = BL32_BASE;
 
     ASSERT(. == ALIGN(PAGE_SIZE),
@@ -149,4 +151,5 @@
     }
 
     ASSERT(. <= BL32_LIMIT, "BL32 image has exceeded its limit.")
+    RAM_REGION_END = .;
 }
diff --git a/bl32/tsp/tsp.ld.S b/bl32/tsp/tsp.ld.S
index a6658dd..b735f45 100644
--- a/bl32/tsp/tsp.ld.S
+++ b/bl32/tsp/tsp.ld.S
@@ -16,6 +16,8 @@
 }
 
 SECTIONS {
+    RAM_REGION_START = ORIGIN(RAM);
+    RAM_REGION_LENGTH = LENGTH(RAM);
     . = BL32_BASE;
 
     ASSERT(. == ALIGN(PAGE_SIZE),
@@ -121,4 +123,5 @@
 #endif /* USE_COHERENT_MEM */
 
     ASSERT(. <= BL32_LIMIT, "BL32 image has exceeded its limit.")
+    RAM_REGION_END = .;
 }
diff --git a/docs/about/maintainers.rst b/docs/about/maintainers.rst
index 0287d6c..b591f2a 100644
--- a/docs/about/maintainers.rst
+++ b/docs/about/maintainers.rst
@@ -2,12 +2,11 @@
 ===================
 
 Trusted Firmware-A (TF-A) is an open governance community project. All
-contributions are ultimately merged by the maintainers listed below. Technical
-ownership of most parts of the codebase falls on the code owners listed
-below. An acknowledgement from these code owners is required before the
-maintainers merge a contribution.
+contributions are reviewed and merged by the community members listed below.
 
-More details may be found in the `Project Maintenance Process`_ document.
+For more details on the roles of `maintainers`, `code owners` and general
+information about code reviews in TF-A project, please refer to the :ref:`Code
+Review Guidelines`.
 
 .. |M| replace:: **Mail**
 .. |G| replace:: **GitHub ID**
@@ -18,6 +17,10 @@
 Maintainers
 -----------
 
+.. note::
+   If you wish to become a maintainer for TF-A project, please refer to the
+   :ref:`Project Maintenance Processes`.
+
 :|M|: Dan Handley <dan.handley@arm.com>
 :|G|: `danh-arm`_
 :|M|: Soby Mathew <soby.mathew@arm.com>
@@ -980,5 +983,3 @@
 .. _bytefire: https://github.com/bytefire
 .. _rupsin01: https://github.com/rupsin01
 .. _jimmy-brisson: https://github.com/theotherjimmy
-
-.. _Project Maintenance Process: https://developer.trustedfirmware.org/w/collaboration/project-maintenance-process/
diff --git a/docs/index.rst b/docs/index.rst
index d5ab8fc..bce9bb7 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -17,6 +17,7 @@
    security_advisories/index
    design_documents/index
    threat_model/index
+   tools/index
    change-log
    glossary
    license
diff --git a/docs/process/code-review-guidelines.rst b/docs/process/code-review-guidelines.rst
index 67a211f..ccdd110 100644
--- a/docs/process/code-review-guidelines.rst
+++ b/docs/process/code-review-guidelines.rst
@@ -1,11 +1,6 @@
 Code Review Guidelines
 ======================
 
-This document provides TF-A specific details about the project's code review
-process. It should be read in conjunction with the `Project Maintenance
-Process`_, which it supplements.
-
-
 Why do we do code reviews?
 --------------------------
 
@@ -23,8 +18,34 @@
 unfairly criticizing or belittling the work of any contributor.
 
 
-Good practices
---------------
+Overview of the code review process
+-----------------------------------
+
+All contributions to Trusted Firmware-A project are reviewed by the community to
+ensure they meet the project's expectations before they get merged, according to
+the `Project Maintenance Process`_ defined for all `Trusted Firmware` projects.
+
+Technical ownership of most parts of the codebase falls on the :ref:`code
+owners`. All patches are ultimately merged by the :ref:`maintainers`.
+
+Approval of a patch is tracked using Gerrit `labels`. For a patch to be merged,
+it must get all of the following votes:
+
+- At least one ``Code-Owner-Review+1`` up-vote, and no ``Code-Owner-Review-1``
+  down-vote.
+
+- At least one ``Maintainer-Review+1`` up-vote, and no ``Maintainer-Review-1``
+  down-vote.
+
+- ``Verified+1`` vote applied by the automated Continuous Integration (CI)
+  system.
+
+Note that, in some instances, the maintainers might give a waiver for some of
+the CI failures and manually override the ``Verified+1`` score.
+
+
+Good practices for all reviewers
+--------------------------------
 
 To ensure the code review gives the greatest possible benefit, participants in
 the project should:
@@ -211,6 +232,6 @@
 
 --------------
 
-*Copyright (c) 2020, Arm Limited. All rights reserved.*
+*Copyright (c) 2020-2023, Arm Limited. All rights reserved.*
 
 .. _Project Maintenance Process: https://developer.trustedfirmware.org/w/collaboration/project-maintenance-process/
diff --git a/docs/process/index.rst b/docs/process/index.rst
index 7914a4e..9b669c0 100644
--- a/docs/process/index.rst
+++ b/docs/process/index.rst
@@ -13,4 +13,5 @@
    contributing
    code-review-guidelines
    faq
+   maintenance
    security-hardening
diff --git a/docs/process/maintenance.rst b/docs/process/maintenance.rst
new file mode 100644
index 0000000..45aada2
--- /dev/null
+++ b/docs/process/maintenance.rst
@@ -0,0 +1,55 @@
+Project Maintenance Processes
+=============================
+
+Trusted Firmware-A (TF-A) project follows the generic `trustedfirmware.org
+Project Maintenance Process`_. The present document complements it by defining
+TF-A project-specific decisions.
+
+How to become a maintainer?
+---------------------------
+
+Qualifying Criteria
+~~~~~~~~~~~~~~~~~~~
+
+To be elligible to become a maintainer for TF-A project, all criteria outlined
+`here`_ must be fullfilled. These are:
+
+- Being an active member of the project for at least a couple of years.
+
+- Having contributed a substantial number of non-trivial and high-quality
+  patches.
+
+- Having reviewed a substantial number of non-trivial patches, preferably in the
+  generic layer, with high-quality constructive feedback.
+
+- Behaving in a professional and polite way, with the best interests of the
+  project at heart.
+
+- Showing a strong will to improve the project and to do the right thing, rather
+  than going for the quick and easy path.
+
+- Participating in design discussions on the development mailing list and during
+  TF-A tech forums calls.
+
+- Having appropriate bandwidth (minimum 2 hours per week) to deal with the workload.
+
+Election Process
+~~~~~~~~~~~~~~~~
+
+To put an individual's name up for election,
+
+#. Send an email to all existing TF-A maintainers, asking whether they have any
+   objections to this individual becoming a TF-A maintainer.
+
+#. Give existing maintainers one calendar week to participate in the discussion.
+
+#. If there are objections, the existing maintainers should try to resolve them
+   amongst themselves. If they cannot, this should be escalated to the
+   trustedfirmware.org Technical Steering Commitee (TSC).
+
+#. If there are no (more) objections, announce the news on the TF-A mailing list
+   and update the list of maintainers on the :ref:`Project
+   Maintenance<maintainers>` page.
+
+.. _trustedfirmware.org Project Maintenance Process: https://developer.trustedfirmware.org/w/collaboration/project-maintenance-process/
+.. _here: https://developer.trustedfirmware.org/w/collaboration/project-maintenance-process/#how-to-become-a-maintainer
diff --git a/docs/tools/index.rst b/docs/tools/index.rst
new file mode 100644
index 0000000..2dee2c0
--- /dev/null
+++ b/docs/tools/index.rst
@@ -0,0 +1,12 @@
+Tools
+=====
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Contents
+
+   memory-layout-tool
+
+--------------
+
+*Copyright (c) 2023, Arm Limited. All rights reserved.*
diff --git a/docs/tools/memory-layout-tool.rst b/docs/tools/memory-layout-tool.rst
new file mode 100644
index 0000000..ce14dab
--- /dev/null
+++ b/docs/tools/memory-layout-tool.rst
@@ -0,0 +1,120 @@
+TF-A Memory Layout Tool
+=======================
+
+TF-A's memory layout tool is a Python script for analyzing the virtual
+memory layout of TF-A builds.
+
+Prerequisites
+~~~~~~~~~~~~~
+
+#. Python (3.8 or later)
+#. `Poetry`_ Python package manager
+
+Getting Started
+~~~~~~~~~~~~~~~
+
+#. Install Poetry
+
+    .. code:: shell
+
+        curl -sSL https://install.python-poetry.org | python3 -
+
+#. Install the required packages
+
+    .. code:: shell
+
+        poetry install --with memory
+
+#. Verify that the tool runs in the installed virtual environment
+
+    .. code:: shell
+
+        poetry run memory --help
+
+Symbol Virtual Map
+~~~~~~~~~~~~~~~~~~
+
+The tool can be used to generate a visualisation of the symbol table. By
+default, it prints the symbols representing the start and end address of the
+main memory regions in an ELF file (i.e. text, bss, rodata) but can be modified
+to print any set of symbols.
+
+.. code:: shell
+
+    $ poetry run memory -s
+    build-path: build/fvp/release
+    Virtual Address Map:
+               +------------__BL1_RAM_END__------------+---------------------------------------+
+               +---------__COHERENT_RAM_END__----------+                                       |
+               +--------__COHERENT_RAM_START__---------+                                       |
+    0x0403b000 +----------__XLAT_TABLE_END__-----------+                                       |
+    0x04036000 +---------__XLAT_TABLE_START__----------+                                       |
+               +--------__BASE_XLAT_TABLE_END__--------+                                       |
+    0x04035600 +--------------__BSS_END__--------------+                                       |
+               +-------__BASE_XLAT_TABLE_START__-------+                                       |
+               +-----__PMF_PERCPU_TIMESTAMP_END__------+                                       |
+               +---------__PMF_TIMESTAMP_END__---------+                                       |
+    0x04035400 +--------__PMF_TIMESTAMP_START__--------+                                       |
+               +-------------__BSS_START__-------------+                                       |
+    0x04034a00 +------------__STACKS_END__-------------+                                       |
+    0x04034500 +-----------__STACKS_START__------------+                                       |
+    0x040344c5 +-----------__DATA_RAM_END__------------+                                       |
+               +-----------__BL1_RAM_START__-----------+                                       |
+    0x04034000 +----------__DATA_RAM_START__-----------+                                       |
+               |                                       +---------__COHERENT_RAM_END__----------+
+               |                                       +--------__COHERENT_RAM_START__---------+
+    0x0402e000 |                                       +----------__XLAT_TABLE_END__-----------+
+    0x04029000 |                                       +---------__XLAT_TABLE_START__----------+
+               |                                       +--------__BASE_XLAT_TABLE_END__--------+
+    0x04028800 |                                       +--------------__BSS_END__--------------+
+               |                                       +-------__BASE_XLAT_TABLE_START__-------+
+               |                                       +-----__PMF_PERCPU_TIMESTAMP_END__------+
+               |                                       +---------__PMF_TIMESTAMP_END__---------+
+    0x04028580 |                                       +--------__PMF_TIMESTAMP_START__--------+
+    0x04028000 |                                       +-------------__BSS_START__-------------+
+    0x04027e40 |                                       +------------__STACKS_END__-------------+
+    0x04027840 |                                       +-----------__STACKS_START__------------+
+    0x04027000 |                                       +------------__RODATA_END__-------------+
+               |                                       +------------__CPU_OPS_END__------------+
+               |                                       +-----------__CPU_OPS_START__-----------+
+               |                                       +--------__FCONF_POPULATOR_END__--------+
+               |                                       +--------------__GOT_END__--------------+
+               |                                       +-------------__GOT_START__-------------+
+               |                                       +---------__PMF_SVC_DESCS_END__---------+
+    0x04026c10 |                                       +--------__PMF_SVC_DESCS_START__--------+
+    0x04026bf8 |                                       +-------__FCONF_POPULATOR_START__-------+
+               |                                       +-----------__RODATA_START__------------+
+    0x04026000 |                                       +-------------__TEXT_END__--------------+
+    0x04021000 |                                       +------------__TEXT_START__-------------+
+    0x000062b5 +------------__BL1_ROM_END__------------+                                       |
+    0x00005df0 +----------__DATA_ROM_START__-----------+                                       |
+               +------------__CPU_OPS_END__------------+                                       |
+               +--------------__GOT_END__--------------+                                       |
+               +-------------__GOT_START__-------------+                                       |
+    0x00005de8 +------------__RODATA_END__-------------+                                       |
+               +-----------__CPU_OPS_START__-----------+                                       |
+               +--------__FCONF_POPULATOR_END__--------+                                       |
+               +---------__PMF_SVC_DESCS_END__---------+                                       |
+    0x00005c98 +--------__PMF_SVC_DESCS_START__--------+                                       |
+    0x00005c80 +-------__FCONF_POPULATOR_START__-------+                                       |
+               +-----------__RODATA_START__------------+                                       |
+    0x00005000 +-------------__TEXT_END__--------------+                                       |
+    0x00000000 +------------__TEXT_START__-------------+---------------------------------------+
+
+Addresses are displayed in hexadecimal by default but can be printed in decimal
+instead with the ``-d`` option.
+
+Because of the length of many of the symbols, the tool defaults to a text width
+of 120 chars. This can be increased if needed with the ``-w`` option.
+
+For more detailed help instructions, run:
+
+.. code:: shell
+
+    poetry run memory --help
+
+--------------
+
+*Copyright (c) 2023, Arm Limited. All rights reserved.*
+
+.. _Poetry: https://python-poetry.org/docs/
diff --git a/drivers/st/crypto/stm32_pka.c b/drivers/st/crypto/stm32_pka.c
index 1e7c42c..9124cf2 100644
--- a/drivers/st/crypto/stm32_pka.c
+++ b/drivers/st/crypto/stm32_pka.c
@@ -33,10 +33,10 @@
 
 #define UINT8_LEN			8U
 #define UINT64_LEN			(UINT8_LEN * sizeof(uint64_t))
-#define WORD_SIZE			(sizeof(uint64_t))
+#define PKA_WORD_SIZE			(sizeof(uint64_t))
 #define OP_NBW_FROM_LEN(len)		(DIV_ROUND_UP_2EVAL((len), UINT64_LEN) + 1)
 #define OP_NBW_FROM_SIZE(s)		OP_NBW_FROM_LEN((s) * UINT8_LEN)
-#define OP_SIZE_FROM_SIZE(s)		(OP_NBW_FROM_SIZE(s) * WORD_SIZE)
+#define OP_SIZE_FROM_SIZE(s)		(OP_NBW_FROM_SIZE(s) * PKA_WORD_SIZE)
 
 #define DT_PKA_COMPAT			"st,stm32-pka64"
 
diff --git a/include/arch/aarch32/asm_macros.S b/include/arch/aarch32/asm_macros.S
index 483f9fe..83e94ca 100644
--- a/include/arch/aarch32/asm_macros.S
+++ b/include/arch/aarch32/asm_macros.S
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016-2018, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2016-2023, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -8,6 +8,7 @@
 
 #include <arch.h>
 #include <common/asm_macros_common.S>
+#include <lib/cpus/cpu_ops.h>
 #include <lib/spinlock.h>
 
 /*
@@ -24,8 +25,6 @@
 	stcopr	_reg, _coproc
 #endif
 
-#define WORD_SIZE	4
-
 	/*
 	 * Co processor register accessors
 	 */
@@ -49,14 +48,14 @@
 	.macro	dcache_line_size  reg, tmp
 	ldcopr	\tmp, CTR
 	ubfx	\tmp, \tmp, #CTR_DMINLINE_SHIFT, #CTR_DMINLINE_WIDTH
-	mov	\reg, #WORD_SIZE
+	mov	\reg, #CPU_WORD_SIZE
 	lsl	\reg, \reg, \tmp
 	.endm
 
 	.macro	icache_line_size  reg, tmp
 	ldcopr	\tmp, CTR
 	and	\tmp, \tmp, #CTR_IMINLINE_MASK
-	mov	\reg, #WORD_SIZE
+	mov	\reg, #CPU_WORD_SIZE
 	lsl	\reg, \reg, \tmp
 	.endm
 
diff --git a/include/lib/cpus/aarch32/cpu_macros.S b/include/lib/cpus/aarch32/cpu_macros.S
index ab2f2c6..096e0b1 100644
--- a/include/lib/cpus/aarch32/cpu_macros.S
+++ b/include/lib/cpus/aarch32/cpu_macros.S
@@ -1,82 +1,13 @@
 /*
- * Copyright (c) 2016-2023, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2016-2023, Arm Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
 #ifndef CPU_MACROS_S
 #define CPU_MACROS_S
 
-#include <arch.h>
-#include <lib/cpus/errata_report.h>
-
-#if defined(IMAGE_BL1) || defined(IMAGE_BL32)  \
-	|| (defined(IMAGE_BL2) && RESET_TO_BL2)
-#define IMAGE_AT_EL3
-#endif
-
-#define CPU_IMPL_PN_MASK	(MIDR_IMPL_MASK << MIDR_IMPL_SHIFT) | \
-				(MIDR_PN_MASK << MIDR_PN_SHIFT)
-
-/* The number of CPU operations allowed */
-#define CPU_MAX_PWR_DWN_OPS		2
-
-/* Special constant to specify that CPU has no reset function */
-#define CPU_NO_RESET_FUNC		0
-
-/* Word size for 32-bit CPUs */
-#define CPU_WORD_SIZE			4
-
-/*
- * Whether errata status needs reporting. Errata status is printed in debug
- * builds for both BL1 and BL32 images.
- */
-#if (defined(IMAGE_BL1) || defined(IMAGE_BL32)) && DEBUG
-# define REPORT_ERRATA	1
-#else
-# define REPORT_ERRATA	0
-#endif
-
-
-	.equ	CPU_MIDR_SIZE, CPU_WORD_SIZE
-	.equ	CPU_RESET_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_PWR_DWN_OPS_SIZE, CPU_WORD_SIZE * CPU_MAX_PWR_DWN_OPS
-	.equ	CPU_ERRATA_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_ERRATA_LOCK_SIZE, CPU_WORD_SIZE
-	.equ	CPU_ERRATA_PRINTED_SIZE, CPU_WORD_SIZE
-
-#ifndef IMAGE_AT_EL3
-	.equ	CPU_RESET_FUNC_SIZE, 0
-#endif
-
-/* The power down core and cluster is needed only in BL32 */
-#ifndef IMAGE_BL32
-	.equ	CPU_PWR_DWN_OPS_SIZE, 0
-#endif
-
-/* Fields required to print errata status  */
-#if !REPORT_ERRATA
-	.equ	CPU_ERRATA_FUNC_SIZE, 0
-#endif
-
-/* Only BL32 requires mutual exclusion and printed flag. */
-#if !(REPORT_ERRATA && defined(IMAGE_BL32))
-	.equ	CPU_ERRATA_LOCK_SIZE, 0
-	.equ	CPU_ERRATA_PRINTED_SIZE, 0
-#endif
-
-
-/*
- * Define the offsets to the fields in cpu_ops structure.
- * Every offset is defined based on the offset and size of the previous
- * field.
- */
-	.equ	CPU_MIDR, 0
-	.equ	CPU_RESET_FUNC, CPU_MIDR + CPU_MIDR_SIZE
-	.equ	CPU_PWR_DWN_OPS, CPU_RESET_FUNC + CPU_RESET_FUNC_SIZE
-	.equ	CPU_ERRATA_FUNC, CPU_PWR_DWN_OPS + CPU_PWR_DWN_OPS_SIZE
-	.equ	CPU_ERRATA_LOCK, CPU_ERRATA_FUNC + CPU_ERRATA_FUNC_SIZE
-	.equ	CPU_ERRATA_PRINTED, CPU_ERRATA_LOCK + CPU_ERRATA_LOCK_SIZE
-	.equ	CPU_OPS_SIZE, CPU_ERRATA_PRINTED + CPU_ERRATA_PRINTED_SIZE
+#include <lib/cpus/cpu_ops.h>
+#include <lib/cpus/errata.h>
 
 	/*
 	 * Write given expressions as words
@@ -142,6 +73,29 @@
 	fill_constants CPU_MAX_PWR_DWN_OPS, \_power_down_ops
 #endif
 
+	/*
+	 * It is possible (although unlikely) that a cpu may have no errata in
+	 * code. In that case the start label will not be defined. The list is
+	 * inteded to be used in a loop, so define it as zero-length for
+	 * predictable behaviour. Since this macro is always called at the end
+	 * of the cpu file (after all errata have been parsed) we can be sure
+	 * that we are at the end of the list. Some cpus call the macro twice,
+	 * so only do this once.
+	 */
+	.pushsection .rodata.errata_entries
+	.ifndef \_name\()_errata_list_start
+		\_name\()_errata_list_start:
+	.endif
+	/* some call this multiple times, so only do this once */
+	.ifndef \_name\()_errata_list_end
+		\_name\()_errata_list_end:
+	.endif
+	.popsection
+
+	/* and now put them in cpu_ops */
+	.word \_name\()_errata_list_start
+	.word \_name\()_errata_list_end
+
 #if REPORT_ERRATA
 	.ifndef \_name\()_cpu_str
 	  /*
@@ -166,6 +120,7 @@
 	 * this class.
 	 */
 	.word \_name\()_errata_report
+	.word \_name\()_cpu_str
 
 #ifdef IMAGE_BL32
 	/* Pointers to errata lock and reported flag */
@@ -228,4 +183,77 @@
 	beq	\_label
 	.endm
 
+/*
+ * NOTE an erratum and CVE id could clash. However, both numbers are very large
+ * and the probablity is minuscule. Working around this makes code very
+ * complicated and extremely difficult to read so it is not considered. In the
+ * unlikely event that this does happen, prepending the CVE id with a 0 should
+ * resolve the conflict
+ */
+
+/*
+ * Add an entry for this erratum to the errata framework
+ *
+ * _cpu:
+ *	Name of cpu as given to declare_cpu_ops
+ *
+ * _cve:
+ *	Whether erratum is a CVE. CVE year if yes, 0 otherwise
+ *
+ * _id:
+ *	Erratum or CVE number. Please combine with the previous field with the
+ *	ERRATUM or CVE macros
+ *
+ * _chosen:
+ *	Compile time flag on whether the erratum is included
+ *
+ * _special:
+ *	The special non-standard name of an erratum
+ */
+.macro add_erratum_entry _cpu:req, _cve:req, _id:req, _chosen:req, _special
+	.pushsection .rodata.errata_entries
+		.align	2
+		.ifndef \_cpu\()_errata_list_start
+		\_cpu\()_errata_list_start:
+		.endif
+
+		/* unused on AArch32, maintain for portability */
+		.word	0
+		/* TODO(errata ABI): this prevents all checker functions from
+		 * being optimised away. Can be done away with unless the ABI
+		 * needs them */
+		.ifnb \_special
+			.word	check_errata_\_special
+		.elseif \_cve
+			.word	check_errata_cve_\_cve\()_\_id
+		.else
+			.word	check_errata_\_id
+		.endif
+		/* Will fit CVEs with up to 10 character in the ID field */
+		.word	\_id
+		.hword	\_cve
+		.byte	\_chosen
+		/* TODO(errata ABI): mitigated field for known but unmitigated
+		 * errata*/
+		.byte	0x1
+	.popsection
+.endm
+
+/*
+ * Maintain compatibility with the old scheme of "each cpu has its own reporter".
+ * TODO remove entirely once all cpus have been converted. This includes the
+ * cpu_ops entry, as print_errata_status can call this directly for all cpus
+ */
+.macro errata_report_shim _cpu:req
+	#if REPORT_ERRATA
+	func \_cpu\()_errata_report
+		push	{r12, lr}
+
+		bl generic_errata_report
+
+		pop	{r12, lr}
+		bx	lr
+	endfunc \_cpu\()_errata_report
+	#endif
+.endm
 #endif /* CPU_MACROS_S */
diff --git a/include/lib/cpus/aarch64/cpu_macros.S b/include/lib/cpus/aarch64/cpu_macros.S
index 041be51..724624c 100644
--- a/include/lib/cpus/aarch64/cpu_macros.S
+++ b/include/lib/cpus/aarch64/cpu_macros.S
@@ -1,95 +1,14 @@
 /*
- * Copyright (c) 2014-2022, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2014-2023, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
 #ifndef CPU_MACROS_S
 #define CPU_MACROS_S
 
-#include <arch.h>
 #include <assert_macros.S>
-#include <lib/cpus/errata_report.h>
-
-#define CPU_IMPL_PN_MASK	(MIDR_IMPL_MASK << MIDR_IMPL_SHIFT) | \
-				(MIDR_PN_MASK << MIDR_PN_SHIFT)
-
-/* The number of CPU operations allowed */
-#define CPU_MAX_PWR_DWN_OPS		2
-
-/* Special constant to specify that CPU has no reset function */
-#define CPU_NO_RESET_FUNC		0
-
-#define CPU_NO_EXTRA1_FUNC		0
-#define CPU_NO_EXTRA2_FUNC		0
-#define CPU_NO_EXTRA3_FUNC		0
-
-/* Word size for 64-bit CPUs */
-#define CPU_WORD_SIZE			8
-
-/*
- * Whether errata status needs reporting. Errata status is printed in debug
- * builds for both BL1 and BL31 images.
- */
-#if (defined(IMAGE_BL1) || defined(IMAGE_BL31)) && DEBUG
-# define REPORT_ERRATA	1
-#else
-# define REPORT_ERRATA	0
-#endif
-
-
-	.equ	CPU_MIDR_SIZE, CPU_WORD_SIZE
-	.equ	CPU_EXTRA1_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_EXTRA2_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_EXTRA3_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_E_HANDLER_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_RESET_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_PWR_DWN_OPS_SIZE, CPU_WORD_SIZE * CPU_MAX_PWR_DWN_OPS
-	.equ	CPU_ERRATA_FUNC_SIZE, CPU_WORD_SIZE
-	.equ	CPU_ERRATA_LOCK_SIZE, CPU_WORD_SIZE
-	.equ	CPU_ERRATA_PRINTED_SIZE, CPU_WORD_SIZE
-	.equ	CPU_REG_DUMP_SIZE, CPU_WORD_SIZE
-
-#ifndef IMAGE_AT_EL3
-	.equ	CPU_RESET_FUNC_SIZE, 0
-#endif
-
-/* The power down core and cluster is needed only in BL31 */
-#ifndef IMAGE_BL31
-	.equ	CPU_PWR_DWN_OPS_SIZE, 0
-#endif
-
-/* Fields required to print errata status. */
-#if !REPORT_ERRATA
-	.equ	CPU_ERRATA_FUNC_SIZE, 0
-#endif
-
-/* Only BL31 requieres mutual exclusion and printed flag.  */
-#if !(REPORT_ERRATA && defined(IMAGE_BL31))
-	.equ	CPU_ERRATA_LOCK_SIZE, 0
-	.equ	CPU_ERRATA_PRINTED_SIZE, 0
-#endif
-
-#if !defined(IMAGE_BL31) || !CRASH_REPORTING
-	.equ	CPU_REG_DUMP_SIZE, 0
-#endif
-
-/*
- * Define the offsets to the fields in cpu_ops structure.
- * Every offset is defined based in the offset and size of the previous
- * field.
- */
-	.equ	CPU_MIDR, 0
-	.equ	CPU_RESET_FUNC, CPU_MIDR + CPU_MIDR_SIZE
-	.equ	CPU_EXTRA1_FUNC, CPU_RESET_FUNC + CPU_RESET_FUNC_SIZE
-	.equ	CPU_EXTRA2_FUNC, CPU_EXTRA1_FUNC + CPU_EXTRA1_FUNC_SIZE
-	.equ	CPU_EXTRA3_FUNC, CPU_EXTRA2_FUNC + CPU_EXTRA2_FUNC_SIZE
-	.equ	CPU_E_HANDLER_FUNC, CPU_EXTRA3_FUNC + CPU_EXTRA3_FUNC_SIZE
-	.equ	CPU_PWR_DWN_OPS, CPU_E_HANDLER_FUNC + CPU_E_HANDLER_FUNC_SIZE
-	.equ	CPU_ERRATA_FUNC, CPU_PWR_DWN_OPS + CPU_PWR_DWN_OPS_SIZE
-	.equ	CPU_ERRATA_LOCK, CPU_ERRATA_FUNC + CPU_ERRATA_FUNC_SIZE
-	.equ	CPU_ERRATA_PRINTED, CPU_ERRATA_LOCK + CPU_ERRATA_LOCK_SIZE
-	.equ	CPU_REG_DUMP, CPU_ERRATA_PRINTED + CPU_ERRATA_PRINTED_SIZE
-	.equ	CPU_OPS_SIZE, CPU_REG_DUMP + CPU_REG_DUMP_SIZE
+#include <lib/cpus/cpu_ops.h>
+#include <lib/cpus/errata.h>
 
 	/*
 	 * Write given expressions as quad words
@@ -172,6 +91,27 @@
 	/* Insert list of functions */
 	fill_constants CPU_MAX_PWR_DWN_OPS, \_power_down_ops
 #endif
+	/*
+	 * It is possible (although unlikely) that a cpu may have no errata in
+	 * code. In that case the start label will not be defined. The list is
+	 * intended to be used in a loop, so define it as zero-length for
+	 * predictable behaviour. Since this macro is always called at the end
+	 * of the cpu file (after all errata have been parsed) we can be sure
+	 * that we are at the end of the list. Some cpus call declare_cpu_ops
+	 * twice, so only do this once.
+	 */
+	.pushsection .rodata.errata_entries
+	.ifndef \_name\()_errata_list_start
+		\_name\()_errata_list_start:
+	.endif
+	.ifndef \_name\()_errata_list_end
+		\_name\()_errata_list_end:
+	.endif
+	.popsection
+
+	/* and now put them in cpu_ops */
+	.quad \_name\()_errata_list_start
+	.quad \_name\()_errata_list_end
 
 #if REPORT_ERRATA
 	.ifndef \_name\()_cpu_str
@@ -192,18 +132,20 @@
 	  .popsection
 	.endif
 
+
 	/*
 	 * Mandatory errata status printing function for CPUs of
 	 * this class.
 	 */
 	.quad \_name\()_errata_report
+	.quad \_name\()_cpu_str
 
 #ifdef IMAGE_BL31
 	/* Pointers to errata lock and reported flag */
 	.quad \_name\()_errata_lock
 	.quad \_name\()_errata_reported
-#endif
-#endif
+#endif /* IMAGE_BL31 */
+#endif /* REPORT_ERRATA */
 
 #if defined(IMAGE_BL31) && CRASH_REPORTING
 	.quad \_name\()_cpu_reg_dump
@@ -229,6 +171,7 @@
 			\_extra1, \_extra2, \_extra3, 0, \_power_down_ops
 	.endm
 
+/* TODO can be deleted once all CPUs have been converted */
 #if REPORT_ERRATA
 	/*
 	 * Print status of a CPU errata
@@ -311,4 +254,322 @@
 	b.eq	\_label
 	.endm
 
+
+/*
+ * Workaround wrappers for errata that apply at reset or runtime. Reset errata
+ * will be applied automatically
+ *
+ * _cpu:
+ *	Name of cpu as given to declare_cpu_ops
+ *
+ * _cve:
+ *	Whether erratum is a CVE. CVE year if yes, 0 otherwise
+ *
+ * _id:
+ *	Erratum or CVE number. Please combine with previous field with ERRATUM
+ *	or CVE macros
+ *
+ * _chosen:
+ *	Compile time flag on whether the erratum is included
+ *
+ * _apply_at_reset:
+ *	Whether the erratum should be automatically applied at reset
+ */
+.macro add_erratum_entry _cpu:req, _cve:req, _id:req, _chosen:req, _apply_at_reset:req
+	.pushsection .rodata.errata_entries
+		.align	3
+		.ifndef \_cpu\()_errata_list_start
+		\_cpu\()_errata_list_start:
+		.endif
+
+		/* check if unused and compile out if no references */
+		.if \_apply_at_reset && \_chosen
+			.quad	erratum_\_cpu\()_\_id\()_wa
+		.else
+			.quad	0
+		.endif
+		/* TODO(errata ABI): this prevents all checker functions from
+		 * being optimised away. Can be done away with unless the ABI
+		 * needs them */
+		.quad	check_erratum_\_cpu\()_\_id
+		/* Will fit CVEs with up to 10 character in the ID field */
+		.word	\_id
+		.hword	\_cve
+		.byte	\_chosen
+		/* TODO(errata ABI): mitigated field for known but unmitigated
+		 * errata */
+		.byte	0x1
+	.popsection
+.endm
+
+.macro _workaround_start _cpu:req, _cve:req, _id:req, _chosen:req, _apply_at_reset:req
+	add_erratum_entry \_cpu, \_cve, \_id, \_chosen, \_apply_at_reset
+
+	func erratum_\_cpu\()_\_id\()_wa
+		mov	x8, x30
+
+		/* save rev_var for workarounds that might need it but don't
+		 * restore to x0 because few will care */
+		mov	x7, x0
+		bl	check_erratum_\_cpu\()_\_id
+		cbz	x0, erratum_\_cpu\()_\_id\()_skip
+.endm
+
+.macro _workaround_end _cpu:req, _id:req
+	erratum_\_cpu\()_\_id\()_skip:
+		ret	x8
+	endfunc erratum_\_cpu\()_\_id\()_wa
+.endm
+
+/*******************************************************************************
+ * Errata workaround wrappers
+ ******************************************************************************/
+/*
+ * Workaround wrappers for errata that apply at reset or runtime. Reset errata
+ * will be applied automatically
+ *
+ * _cpu:
+ *	Name of cpu as given to declare_cpu_ops
+ *
+ * _cve:
+ *	Whether erratum is a CVE. CVE year if yes, 0 otherwise
+ *
+ * _id:
+ *	Erratum or CVE number. Please combine with previous field with ERRATUM
+ *	or CVE macros
+ *
+ * _chosen:
+ *	Compile time flag on whether the erratum is included
+ *
+ * in body:
+ *	clobber x0 to x7 (please only use those)
+ *	argument x7 - cpu_rev_var
+ *
+ * _wa clobbers: x0-x8 (PCS compliant)
+ */
+.macro workaround_reset_start _cpu:req, _cve:req, _id:req, _chosen:req
+	_workaround_start \_cpu, \_cve, \_id, \_chosen, 1
+.endm
+
+/*
+ * See `workaround_reset_start` for usage info. Additional arguments:
+ *
+ * _midr:
+ *	Check if CPU's MIDR matches the CPU it's meant for. Must be specified
+ *	for errata applied in generic code
+ */
+.macro workaround_runtime_start _cpu:req, _cve:req, _id:req, _chosen:req, _midr
+	/*
+	 * Let errata specify if they need MIDR checking. Sadly, storing the
+	 * MIDR in an .equ to retrieve automatically blows up as it stores some
+	 * brackets in the symbol
+	 */
+	.ifnb \_midr
+		jump_if_cpu_midr \_midr, 1f
+		b	erratum_\_cpu\()_\_id\()_skip
+
+		1:
+	.endif
+	_workaround_start \_cpu, \_cve, \_id, \_chosen, 0
+.endm
+
+/*
+ * Usage and arguments identical to `workaround_reset_start`. The _cve argument
+ * is kept here so the same #define can be used as that macro
+ */
+.macro workaround_reset_end _cpu:req, _cve:req, _id:req
+	_workaround_end \_cpu, \_id
+.endm
+
+/*
+ * See `workaround_reset_start` for usage info. The _cve argument is kept here
+ * so the same #define can be used as that macro. Additional arguments:
+ *
+ * _no_isb:
+ *	Optionally do not include the trailing isb. Please disable with the
+ *	NO_ISB macro
+ */
+.macro workaround_runtime_end _cpu:req, _cve:req, _id:req, _no_isb
+	/*
+	 * Runtime errata do not have a reset function to call the isb for them
+	 * and missing the isb could be very problematic. It is also likely as
+	 * they tend to be scattered in generic code.
+	 */
+	.ifb \_no_isb
+		isb
+	.endif
+	_workaround_end \_cpu, \_id
+.endm
+
+/*******************************************************************************
+ * Errata workaround helpers
+ ******************************************************************************/
+/*
+ * Set a bit in a system register. Can set multiple bits but is limited by the
+ *  way the ORR instruction encodes them.
+ *
+ * _reg:
+ *	Register to write to
+ *
+ * _bit:
+ *	Bit to set. Please use a descriptive #define
+ *
+ * _assert:
+ *	Optionally whether to read back and assert that the bit has been
+ *	written. Please disable with NO_ASSERT macro
+ *
+ * clobbers: x1
+ */
+.macro sysreg_bit_set _reg:req, _bit:req, _assert=1
+	mrs	x1, \_reg
+	orr	x1, x1, #\_bit
+	msr	\_reg, x1
+.endm
+
+/*
+ * Apply erratum
+ *
+ * _cpu:
+ *	Name of cpu as given to declare_cpu_ops
+ *
+ * _cve:
+ *	Whether erratum is a CVE. CVE year if yes, 0 otherwise
+ *
+ * _id:
+ *	Erratum or CVE number. Please combine with previous field with ERRATUM
+ *	or CVE macros
+ *
+ * _chosen:
+ *	Compile time flag on whether the erratum is included
+ *
+ * clobbers: x0-x9 (PCS compliant)
+ */
+.macro apply_erratum _cpu:req, _cve:req, _id:req, _chosen:req
+	.if \_chosen
+		mov	x9, x30
+		bl	cpu_get_rev_var
+		bl	erratum_\_cpu\()_\_id\()_wa
+		mov	x30, x9
+
+	.endif
+.endm
+
+/*
+ * Helpers to select which revisions errata apply to. Don't leave a link
+ * register as the cpu_rev_var_*** will call the ret and we can save on one.
+ *
+ * _cpu:
+ *	Name of cpu as given to declare_cpu_ops
+ *
+ * _cve:
+ *	Whether erratum is a CVE. CVE year if yes, 0 otherwise
+ *
+ * _id:
+ *	Erratum or CVE number. Please combine with previous field with ERRATUM
+ *	or CVE macros
+ *
+ * _rev_num:
+ *	Revision to apply to
+ *
+ * in body:
+ *	clobber: x0 to x4
+ *	argument: x0 - cpu_rev_var
+ */
+.macro check_erratum_ls _cpu:req, _cve:req, _id:req, _rev_num:req
+	func check_erratum_\_cpu\()_\_id
+		mov	x1, #\_rev_num
+		b	cpu_rev_var_ls
+	endfunc check_erratum_\_cpu\()_\_id
+.endm
+
+.macro check_erratum_hs _cpu:req, _cve:req, _id:req, _rev_num:req
+	func check_erratum_\_cpu\()_\_id
+		mov	x1, #\_rev_num
+		b	cpu_rev_var_hs
+	endfunc check_erratum_\_cpu\()_\_id
+.endm
+
+.macro check_erratum_range _cpu:req, _cve:req, _id:req, _rev_num_lo:req, _rev_num_hi:req
+	func check_erratum_\_cpu\()_\_id
+		mov	x1, #\_rev_num_lo
+		mov	x2, #\_rev_num_hi
+		b	cpu_rev_var_range
+	endfunc check_erratum_\_cpu\()_\_id
+.endm
+
+/*******************************************************************************
+ * CPU reset function wrapper
+ ******************************************************************************/
+
+/*
+ * Wrapper to automatically apply all reset-time errata. Will end with an isb.
+ *
+ * _cpu:
+ *	Name of cpu as given to declare_cpu_ops
+ *
+ * in body:
+ *	clobber x8 to x14
+ *	argument x14 - cpu_rev_var
+ */
+.macro cpu_reset_func_start _cpu:req
+	func \_cpu\()_reset_func
+		mov	x15, x30
+		bl	cpu_get_rev_var
+		mov	x14, x0
+
+		/* short circuit the location to avoid searching the list */
+		adrp	x12, \_cpu\()_errata_list_start
+		add	x12, x12, :lo12:\_cpu\()_errata_list_start
+		adrp	x13, \_cpu\()_errata_list_end
+		add	x13, x13, :lo12:\_cpu\()_errata_list_end
+
+	errata_begin:
+		/* if head catches up with end of list, exit */
+		cmp	x12, x13
+		b.eq	errata_end
+
+		ldr	x10, [x12, #ERRATUM_WA_FUNC]
+		/* TODO(errata ABI): check mitigated and checker function fields
+		 * for 0 */
+		ldrb	w11, [x12, #ERRATUM_CHOSEN]
+
+		/* skip if not chosen */
+		cbz	x11, 1f
+		/* skip if runtime erratum */
+		cbz	x10, 1f
+
+		/* put cpu revision in x0 and call workaround */
+		mov	x0, x14
+		blr	x10
+	1:
+		add	x12, x12, #ERRATUM_ENTRY_SIZE
+		b	errata_begin
+	errata_end:
+.endm
+
+.macro cpu_reset_func_end _cpu:req
+		isb
+		ret	x15
+	endfunc \_cpu\()_reset_func
+.endm
+
+/*
+ * Maintain compatibility with the old scheme of each cpu has its own reporting.
+ * TODO remove entirely once all cpus have been converted. This includes the
+ * cpu_ops entry, as print_errata_status can call this directly for all cpus
+ */
+.macro errata_report_shim _cpu:req
+	#if REPORT_ERRATA
+	func \_cpu\()_errata_report
+		/* normal stack frame for pretty debugging */
+		stp	x29, x30, [sp, #-16]!
+		mov	x29, sp
+
+		bl	generic_errata_report
+
+		ldp	x29, x30, [sp], #16
+		ret
+	endfunc \_cpu\()_errata_report
+	#endif
+.endm
 #endif /* CPU_MACROS_S */
diff --git a/include/lib/cpus/cpu_ops.h b/include/lib/cpus/cpu_ops.h
new file mode 100644
index 0000000..8b36ff1
--- /dev/null
+++ b/include/lib/cpus/cpu_ops.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef CPU_OPS_H
+#define CPU_OPS_H
+
+#include <arch.h>
+
+#define CPU_IMPL_PN_MASK	(MIDR_IMPL_MASK << MIDR_IMPL_SHIFT) | \
+				(MIDR_PN_MASK << MIDR_PN_SHIFT)
+
+/* Hardcode to keep compatible with assembly. sizeof(uintptr_t) */
+#if __aarch64__
+#define CPU_WORD_SIZE			8
+#else
+#define CPU_WORD_SIZE			4
+#endif /* __aarch64__ */
+
+/* The number of CPU operations allowed */
+#define CPU_MAX_PWR_DWN_OPS		2
+/* Special constant to specify that CPU has no reset function */
+#define CPU_NO_RESET_FUNC		0
+
+#if __aarch64__
+#define CPU_NO_EXTRA1_FUNC		0
+#define CPU_NO_EXTRA2_FUNC		0
+#define CPU_NO_EXTRA3_FUNC		0
+#endif /* __aarch64__ */
+
+
+/*
+ * Define the sizes of the fields in the cpu_ops structure. Word size is set per
+ * Aarch so keep these definitions the same and each can include whatever it
+ * needs.
+ */
+#define CPU_MIDR_SIZE		CPU_WORD_SIZE
+#ifdef IMAGE_AT_EL3
+#define CPU_RESET_FUNC_SIZE	CPU_WORD_SIZE
+#else
+#define CPU_RESET_FUNC_SIZE	0
+#endif /* IMAGE_AT_EL3 */
+#define CPU_EXTRA1_FUNC_SIZE	CPU_WORD_SIZE
+#define CPU_EXTRA2_FUNC_SIZE	CPU_WORD_SIZE
+#define CPU_EXTRA3_FUNC_SIZE	CPU_WORD_SIZE
+#define CPU_E_HANDLER_FUNC_SIZE CPU_WORD_SIZE
+/* The power down core and cluster is needed only in BL31 and BL32 */
+#if defined(IMAGE_BL31) || defined(IMAGE_BL32)
+#define CPU_PWR_DWN_OPS_SIZE	CPU_WORD_SIZE * CPU_MAX_PWR_DWN_OPS
+#else
+#define CPU_PWR_DWN_OPS_SIZE	0
+#endif /* defined(IMAGE_BL31) || defined(IMAGE_BL32) */
+
+#define CPU_ERRATA_LIST_START_SIZE	CPU_WORD_SIZE
+#define CPU_ERRATA_LIST_END_SIZE	CPU_WORD_SIZE
+/* Fields required to print errata status  */
+#if REPORT_ERRATA
+#define CPU_ERRATA_FUNC_SIZE	CPU_WORD_SIZE
+#define CPU_CPU_STR_SIZE	CPU_WORD_SIZE
+/* BL1 doesn't require mutual exclusion and printed flag. */
+#if defined(IMAGE_BL31) || defined(IMAGE_BL32)
+#define CPU_ERRATA_LOCK_SIZE	CPU_WORD_SIZE
+#define CPU_ERRATA_PRINTED_SIZE	CPU_WORD_SIZE
+#else
+#define CPU_ERRATA_LOCK_SIZE	0
+#define CPU_ERRATA_PRINTED_SIZE	0
+#endif /* defined(IMAGE_BL31) || defined(IMAGE_BL32) */
+#else
+#define CPU_ERRATA_FUNC_SIZE	0
+#define CPU_CPU_STR_SIZE	0
+#define CPU_ERRATA_LOCK_SIZE	0
+#define CPU_ERRATA_PRINTED_SIZE	0
+#endif /* REPORT_ERRATA */
+
+#if defined(IMAGE_BL31) && CRASH_REPORTING
+#define CPU_REG_DUMP_SIZE	CPU_WORD_SIZE
+#else
+#define CPU_REG_DUMP_SIZE	0
+#endif /* defined(IMAGE_BL31) && CRASH_REPORTING */
+
+
+/*
+ * Define the offsets to the fields in cpu_ops structure. Every offset is
+ * defined based on the offset and size of the previous field.
+ */
+#define CPU_MIDR		0
+#define CPU_RESET_FUNC		CPU_MIDR + CPU_MIDR_SIZE
+#if __aarch64__
+#define CPU_EXTRA1_FUNC		CPU_RESET_FUNC + CPU_RESET_FUNC_SIZE
+#define CPU_EXTRA2_FUNC		CPU_EXTRA1_FUNC + CPU_EXTRA1_FUNC_SIZE
+#define CPU_EXTRA3_FUNC		CPU_EXTRA2_FUNC + CPU_EXTRA2_FUNC_SIZE
+#define CPU_E_HANDLER_FUNC	CPU_EXTRA3_FUNC + CPU_EXTRA3_FUNC_SIZE
+#define CPU_PWR_DWN_OPS		CPU_E_HANDLER_FUNC + CPU_E_HANDLER_FUNC_SIZE
+#else
+#define CPU_PWR_DWN_OPS		CPU_RESET_FUNC + CPU_RESET_FUNC_SIZE
+#endif /* __aarch64__ */
+#define CPU_ERRATA_LIST_START	CPU_PWR_DWN_OPS + CPU_PWR_DWN_OPS_SIZE
+#define CPU_ERRATA_LIST_END	CPU_ERRATA_LIST_START + CPU_ERRATA_LIST_START_SIZE
+#define CPU_ERRATA_FUNC		CPU_ERRATA_LIST_END + CPU_ERRATA_LIST_END_SIZE
+#define CPU_CPU_STR		CPU_ERRATA_FUNC + CPU_ERRATA_FUNC_SIZE
+#define CPU_ERRATA_LOCK		CPU_CPU_STR + CPU_CPU_STR_SIZE
+#define CPU_ERRATA_PRINTED	CPU_ERRATA_LOCK + CPU_ERRATA_LOCK_SIZE
+#if __aarch64__
+#define CPU_REG_DUMP		CPU_ERRATA_PRINTED + CPU_ERRATA_PRINTED_SIZE
+#define CPU_OPS_SIZE		CPU_REG_DUMP + CPU_REG_DUMP_SIZE
+#else
+#define CPU_OPS_SIZE		CPU_ERRATA_PRINTED + CPU_ERRATA_PRINTED_SIZE
+#endif /* __aarch64__ */
+
+#ifndef __ASSEMBLER__
+#include <lib/cassert.h>
+#include <lib/spinlock.h>
+
+struct cpu_ops {
+	unsigned long midr;
+#ifdef IMAGE_AT_EL3
+	void (*reset_func)(void);
+#endif /* IMAGE_AT_EL3 */
+#if __aarch64__
+	void (*extra1_func)(void);
+	void (*extra2_func)(void);
+	void (*extra3_func)(void);
+	void (*e_handler_func)(long es);
+#endif /* __aarch64__ */
+#if (defined(IMAGE_BL31) || defined(IMAGE_BL32)) && CPU_MAX_PWR_DWN_OPS
+	void (*pwr_dwn_ops[CPU_MAX_PWR_DWN_OPS])(void);
+#endif /* (defined(IMAGE_BL31) || defined(IMAGE_BL32)) && CPU_MAX_PWR_DWN_OPS */
+	void *errata_list_start;
+	void *errata_list_end;
+#if REPORT_ERRATA
+	void (*errata_func)(void);
+	char *cpu_str;
+#if defined(IMAGE_BL31) || defined(IMAGE_BL32)
+	spinlock_t *errata_lock;
+	unsigned int *errata_reported;
+#endif /* defined(IMAGE_BL31) || defined(IMAGE_BL32) */
+#endif /* REPORT_ERRATA */
+#if defined(IMAGE_BL31) && CRASH_REPORTING
+	void (*reg_dump)(void);
+#endif /* defined(IMAGE_BL31) && CRASH_REPORTING */
+} __packed;
+
+CASSERT(sizeof(struct cpu_ops) == CPU_OPS_SIZE,
+	assert_cpu_ops_asm_c_different_sizes);
+
+long cpu_get_rev_var(void);
+void *get_cpu_ops_ptr(void);
+
+#endif /* __ASSEMBLER__ */
+#endif /* CPU_OPS_H */
diff --git a/include/lib/cpus/errata.h b/include/lib/cpus/errata.h
new file mode 100644
index 0000000..f8f9555
--- /dev/null
+++ b/include/lib/cpus/errata.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2017-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef ERRATA_REPORT_H
+#define ERRATA_REPORT_H
+
+#include <lib/cpus/cpu_ops.h>
+
+
+#define ERRATUM_WA_FUNC_SIZE	CPU_WORD_SIZE
+#define ERRATUM_CHECK_FUNC_SIZE	CPU_WORD_SIZE
+#define ERRATUM_ID_SIZE		4
+#define ERRATUM_CVE_SIZE	2
+#define ERRATUM_CHOSEN_SIZE	1
+#define ERRATUM_MITIGATED_SIZE	1
+
+#define ERRATUM_WA_FUNC		0
+#define ERRATUM_CHECK_FUNC	ERRATUM_WA_FUNC + ERRATUM_WA_FUNC_SIZE
+#define ERRATUM_ID		ERRATUM_CHECK_FUNC + ERRATUM_CHECK_FUNC_SIZE
+#define ERRATUM_CVE		ERRATUM_ID + ERRATUM_ID_SIZE
+#define ERRATUM_CHOSEN		ERRATUM_CVE + ERRATUM_CVE_SIZE
+#define ERRATUM_MITIGATED	ERRATUM_CHOSEN + ERRATUM_CHOSEN_SIZE
+#define ERRATUM_ENTRY_SIZE	ERRATUM_MITIGATED + ERRATUM_MITIGATED_SIZE
+
+#ifndef __ASSEMBLER__
+#include <lib/cassert.h>
+
+void print_errata_status(void);
+void errata_print_msg(unsigned int status, const char *cpu, const char *id);
+
+/*
+ * NOTE that this structure will be different on AArch32 and AArch64. The
+ * uintptr_t will reflect the change and the alignment will be correct in both.
+ */
+struct erratum_entry {
+	uintptr_t (*wa_func)(uint64_t cpu_rev);
+	uintptr_t (*check_func)(uint64_t cpu_rev);
+	/* Will fit CVEs with up to 10 character in the ID field */
+	uint32_t id;
+	/* Denote CVEs with their year or errata with 0 */
+	uint16_t cve;
+	uint8_t chosen;
+	/* TODO(errata ABI): placeholder for the mitigated field */
+	uint8_t _mitigated;
+} __packed;
+
+CASSERT(sizeof(struct erratum_entry) == ERRATUM_ENTRY_SIZE,
+	assert_erratum_entry_asm_c_different_sizes);
+#else
+
+/*
+ * errata framework macro helpers
+ *
+ * NOTE an erratum and CVE id could clash. However, both numbers are very large
+ * and the probablity is minuscule. Working around this makes code very
+ * complicated and extremely difficult to read so it is not considered. In the
+ * unlikely event that this does happen, prepending the CVE id with a 0 should
+ * resolve the conflict
+ */
+#define ERRATUM(id)		0, id
+#define CVE(year, id)		year, id
+#define NO_ISB			1
+#define NO_ASSERT		0
+
+#endif /* __ASSEMBLER__ */
+
+/* Errata status */
+#define ERRATA_NOT_APPLIES	0
+#define ERRATA_APPLIES		1
+#define ERRATA_MISSING		2
+
+/* Macro to get CPU revision code for checking errata version compatibility. */
+#define CPU_REV(r, p)		((r << 4) | p)
+
+#endif /* ERRATA_REPORT_H */
diff --git a/include/lib/cpus/errata_report.h b/include/lib/cpus/errata_report.h
deleted file mode 100644
index efdedf0..0000000
--- a/include/lib/cpus/errata_report.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-
-#ifndef ERRATA_REPORT_H
-#define ERRATA_REPORT_H
-
-#ifndef __ASSEMBLER__
-
-#include <arch.h>
-#include <arch_helpers.h>
-#include <lib/spinlock.h>
-#include <lib/utils_def.h>
-
-#if DEBUG
-void print_errata_status(void);
-#else
-static inline void print_errata_status(void) {}
-#endif
-
-void errata_print_msg(unsigned int status, const char *cpu, const char *id);
-int errata_needs_reporting(spinlock_t *lock, uint32_t *reported);
-
-#endif /* __ASSEMBLER__ */
-
-/* Errata status */
-#define ERRATA_NOT_APPLIES	0
-#define ERRATA_APPLIES		1
-#define ERRATA_MISSING		2
-
-/* Macro to get CPU revision code for checking errata version compatibility. */
-#define CPU_REV(r, p)		((r << 4) | p)
-
-#endif /* ERRATA_REPORT_H */
diff --git a/lib/cpus/aarch32/cpu_helpers.S b/lib/cpus/aarch32/cpu_helpers.S
index e25ce2a..05bc5d9 100644
--- a/lib/cpus/aarch32/cpu_helpers.S
+++ b/lib/cpus/aarch32/cpu_helpers.S
@@ -9,6 +9,7 @@
 #include <assert_macros.S>
 #include <cpu_macros.S>
 #include <common/bl_common.h>
+#include <lib/cpus/cpu_ops.h>
 #include <lib/el3_runtime/cpu_data.h>
 
 #if defined(IMAGE_BL1) || defined(IMAGE_BL32) || \
@@ -204,62 +205,3 @@
 	movlt	r0, #ERRATA_NOT_APPLIES
 	bx	lr
 endfunc cpu_rev_var_hs
-
-#if REPORT_ERRATA
-/*
- * void print_errata_status(void);
- *
- * Function to print errata status for CPUs of its class. Must be called only:
- *
- *   - with MMU and data caches are enabled;
- *   - after cpu_ops have been initialized in per-CPU data.
- */
-	.globl print_errata_status
-func print_errata_status
-	/* r12 is pushed only for the sake of 8-byte stack alignment */
-	push	{r4, r5, r12, lr}
-#ifdef IMAGE_BL1
-	/*
-	 * BL1 doesn't have per-CPU data. So retrieve the CPU operations
-	 * directly.
-	 */
-	bl	get_cpu_ops_ptr
-	ldr	r0, [r0, #CPU_ERRATA_FUNC]
-	cmp	r0, #0
-	blxne	r0
-#else
-	/*
-	 * Retrieve pointer to cpu_ops, and further, the errata printing
-	 * function. If it's non-NULL, jump to the function in turn.
-	 */
-	bl	_cpu_data
-#if ENABLE_ASSERTIONS
-	cmp	r0, #0
-	ASM_ASSERT(ne)
-#endif
-	ldr	r1, [r0, #CPU_DATA_CPU_OPS_PTR]
-#if ENABLE_ASSERTIONS
-	cmp	r1, #0
-	ASM_ASSERT(ne)
-#endif
-	ldr	r0, [r1, #CPU_ERRATA_FUNC]
-	cmp	r0, #0
-	beq	1f
-
-	mov	r4, r0
-
-	/*
-	 * Load pointers to errata lock and printed flag. Call
-	 * errata_needs_reporting to check whether this CPU needs to report
-	 * errata status pertaining to its class.
-	 */
-	ldr	r0, [r1, #CPU_ERRATA_LOCK]
-	ldr	r1, [r1, #CPU_ERRATA_PRINTED]
-	bl	errata_needs_reporting
-	cmp	r0, #0
-	blxne	r4
-1:
-#endif
-	pop	{r4, r5, r12, pc}
-endfunc print_errata_status
-#endif
diff --git a/lib/cpus/aarch64/cortex_a53.S b/lib/cpus/aarch64/cortex_a53.S
index df11d86..ecaf422 100644
--- a/lib/cpus/aarch64/cortex_a53.S
+++ b/lib/cpus/aarch64/cortex_a53.S
@@ -9,8 +9,8 @@
 #include <common/debug.h>
 #include <cortex_a53.h>
 #include <cpu_macros.S>
-#include <lib/cpus/errata_report.h>
 #include <plat_macros.S>
+#include <lib/cpus/errata.h>
 
 #if A53_DISABLE_NON_TEMPORAL_HINT
 #undef ERRATA_A53_836870
diff --git a/lib/cpus/aarch64/cpu_helpers.S b/lib/cpus/aarch64/cpu_helpers.S
index 0a03e38..a4285ed 100644
--- a/lib/cpus/aarch64/cpu_helpers.S
+++ b/lib/cpus/aarch64/cpu_helpers.S
@@ -10,7 +10,8 @@
 #include <common/bl_common.h>
 #include <common/debug.h>
 #include <cpu_macros.S>
-#include <lib/cpus/errata_report.h>
+#include <lib/cpus/cpu_ops.h>
+#include <lib/cpus/errata.h>
 #include <lib/el3_runtime/cpu_data.h>
 
  /* Reset fn is needed in BL at reset vector */
@@ -279,72 +280,6 @@
 	ret
 endfunc cpu_rev_var_range
 
-#if REPORT_ERRATA
-/*
- * void print_errata_status(void);
- *
- * Function to print errata status for CPUs of its class. Must be called only:
- *
- *   - with MMU and data caches are enabled;
- *   - after cpu_ops have been initialized in per-CPU data.
- */
-	.globl print_errata_status
-func print_errata_status
-#ifdef IMAGE_BL1
-	/*
-	 * BL1 doesn't have per-CPU data. So retrieve the CPU operations
-	 * directly.
-	 */
-	stp	xzr, x30, [sp, #-16]!
-	bl	get_cpu_ops_ptr
-	ldp	xzr, x30, [sp], #16
-	ldr	x1, [x0, #CPU_ERRATA_FUNC]
-	cbnz	x1, .Lprint
-#else
-	/*
-	 * Retrieve pointer to cpu_ops from per-CPU data, and further, the
-	 * errata printing function. If it's non-NULL, jump to the function in
-	 * turn.
-	 */
-	mrs	x0, tpidr_el3
-#if ENABLE_ASSERTIONS
-	cmp	x0, #0
-	ASM_ASSERT(ne)
-#endif
-	ldr	x1, [x0, #CPU_DATA_CPU_OPS_PTR]
-#if ENABLE_ASSERTIONS
-	cmp	x1, #0
-	ASM_ASSERT(ne)
-#endif
-	ldr	x0, [x1, #CPU_ERRATA_FUNC]
-	cbz	x0, .Lnoprint
-
-	/*
-	 * Printing errata status requires atomically testing the printed flag.
-	 */
-	stp	x19, x30, [sp, #-16]!
-	mov	x19, x0
-
-	/*
-	 * Load pointers to errata lock and printed flag. Call
-	 * errata_needs_reporting to check whether this CPU needs to report
-	 * errata status pertaining to its class.
-	 */
-	ldr	x0, [x1, #CPU_ERRATA_LOCK]
-	ldr	x1, [x1, #CPU_ERRATA_PRINTED]
-	bl	errata_needs_reporting
-	mov	x1, x19
-	ldp	x19, x30, [sp], #16
-	cbnz	x0, .Lprint
-#endif
-.Lnoprint:
-	ret
-.Lprint:
-	/* Jump to errata reporting function for this CPU */
-	br	x1
-endfunc print_errata_status
-#endif
-
 /*
  * int check_wa_cve_2017_5715(void);
  *
diff --git a/lib/cpus/aarch64/dsu_helpers.S b/lib/cpus/aarch64/dsu_helpers.S
index 419b6ea..b7e028a 100644
--- a/lib/cpus/aarch64/dsu_helpers.S
+++ b/lib/cpus/aarch64/dsu_helpers.S
@@ -6,7 +6,7 @@
 
 #include <asm_macros.S>
 #include <dsu_def.h>
-#include <lib/cpus/errata_report.h>
+#include <lib/cpus/errata.h>
 
 	/* -----------------------------------------------------------------------
 	 * DSU erratum 798953 check function
diff --git a/lib/cpus/errata_report.c b/lib/cpus/errata_report.c
index 5f41aee..a37ba81 100644
--- a/lib/cpus/errata_report.c
+++ b/lib/cpus/errata_report.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2023, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2017-2023, Arm Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -11,7 +11,8 @@
 
 #include <arch_helpers.h>
 #include <common/debug.h>
-#include <lib/cpus/errata_report.h>
+#include <lib/cpus/cpu_ops.h>
+#include <lib/cpus/errata.h>
 #include <lib/el3_runtime/cpu_data.h>
 #include <lib/spinlock.h>
 
@@ -30,11 +31,93 @@
 /* Errata format: BL stage, CPU, errata ID, message */
 #define ERRATA_FORMAT	"%s: %s: CPU workaround for %s was %s\n"
 
+#define CVE_FORMAT	"%s: %s: CPU workaround for CVE %u_%u was %s\n"
+#define ERRATUM_FORMAT	"%s: %s: CPU workaround for erratum %u was %s\n"
+
+#define PRINT_STATUS_DISPATCH(status, ...)					\
+	do {									\
+		assert(status <= ERRATA_MISSING);				\
+		switch (status) {						\
+		case ERRATA_NOT_APPLIES:					\
+			VERBOSE(__VA_ARGS__, "not applied");			\
+			break;							\
+		case ERRATA_APPLIES:						\
+			INFO(__VA_ARGS__, "applied");				\
+			break;							\
+		case ERRATA_MISSING:						\
+			WARN(__VA_ARGS__, "missing!");				\
+			break;							\
+		}								\
+	} while (0)
+
+
+#if !REPORT_ERRATA
+void print_errata_status(void) {}
+#else /* !REPORT_ERRATA */
+/* New errata status message printer */
+void __unused generic_errata_report(void)
+{
+	struct cpu_ops *cpu_ops = get_cpu_ops_ptr();
+	struct erratum_entry *entry = cpu_ops->errata_list_start;
+	struct erratum_entry *end = cpu_ops->errata_list_end;
+	long rev_var = cpu_get_rev_var();
+	uint32_t last_erratum_id = 0;
+	uint16_t last_cve_yr = 0;
+	bool check_cve = false;
+	/* unused because assert goes away on release */
+	bool failed __unused = false;
+
+	for (; entry != end; entry += 1) {
+		uint64_t status = entry->check_func(rev_var);
+
+		assert(entry->id != 0);
+
+		/*
+		 * Errata workaround has not been compiled in. If the errata
+		 * would have applied had it been compiled in, print its status
+		 * as missing.
+		 */
+		if (status == ERRATA_APPLIES && entry->chosen == 0) {
+			status = ERRATA_MISSING;
+		}
+
+		if (entry->cve) {
+			PRINT_STATUS_DISPATCH(status, CVE_FORMAT, BL_STRING,
+				cpu_ops->cpu_str, entry->cve, entry->id);
+
+			if (last_cve_yr > entry->cve ||
+			   (last_cve_yr == entry->cve && last_erratum_id >= entry->id)) {
+				ERROR("CVE %u_%u was out of order!\n",
+				      entry->cve, entry->id);
+				failed = true;
+			}
+			check_cve = true;
+			last_cve_yr = entry->cve;
+		} else {
+			PRINT_STATUS_DISPATCH(status, ERRATUM_FORMAT, BL_STRING,
+				cpu_ops->cpu_str, entry->id);
+
+			if (last_erratum_id >= entry->id || check_cve) {
+				ERROR("Erratum %u was out of order!\n",
+				      entry->id);
+				failed = true;
+			}
+		}
+		last_erratum_id = entry->id;
+	}
+
+	/*
+	 * enforce errata and CVEs are in ascending order and that CVEs are
+	 * after errata
+	 */
+	assert(!failed);
+}
+
 /*
  * Returns whether errata needs to be reported. Passed arguments are private to
  * a CPU type.
  */
-int errata_needs_reporting(spinlock_t *lock, uint32_t *reported)
+static __unused int errata_needs_reporting(spinlock_t *lock, uint32_t *reported)
 {
 	bool report_now;
 
@@ -56,14 +139,44 @@
 }
 
 /*
- * Print errata status message.
- *
- * Unknown: WARN
- * Missing: WARN
- * Applied: INFO
- * Not applied: VERBOSE
+ * Function to print errata status for the calling CPU (and others of the same
+ * type). Must be called only:
+ *   - when MMU and data caches are enabled;
+ *   - after cpu_ops have been initialized in per-CPU data.
+ */
+void print_errata_status(void)
+{
+	struct cpu_ops *cpu_ops;
+#ifdef IMAGE_BL1
+	/*
+	 * BL1 doesn't have per-CPU data. So retrieve the CPU operations
+	 * directly.
+	 */
+	cpu_ops = get_cpu_ops_ptr();
+
+	if (cpu_ops->errata_func != NULL) {
+		cpu_ops->errata_func();
+	}
+#else /* IMAGE_BL1 */
+	cpu_ops = (void *) get_cpu_data(cpu_ops_ptr);
+
+	assert(cpu_ops != NULL);
+
+	if (cpu_ops->errata_func == NULL) {
+		return;
+	}
+
+	if (errata_needs_reporting(cpu_ops->errata_lock, cpu_ops->errata_reported)) {
+		cpu_ops->errata_func();
+	}
+#endif /* IMAGE_BL1 */
+}
+
+/*
+ * Old errata status message printer
+ * TODO: remove once all cpus have been converted to the new printing method
  */
-void errata_print_msg(unsigned int status, const char *cpu, const char *id)
+void __unused errata_print_msg(unsigned int status, const char *cpu, const char *id)
 {
 	/* Errata status strings */
 	static const char *const errata_status_str[] = {
@@ -99,3 +212,4 @@
 		break;
 	}
 }
+#endif /* !REPORT_ERRATA */
diff --git a/lib/psci/psci_setup.c b/lib/psci/psci_setup.c
index 16d6e45..1f93cc9 100644
--- a/lib/psci/psci_setup.c
+++ b/lib/psci/psci_setup.c
@@ -11,8 +11,8 @@
 #include <arch_helpers.h>
 #include <common/bl_common.h>
 #include <context.h>
+#include <lib/cpus/errata.h>
 #include <lib/el3_runtime/context_mgmt.h>
-#include <lib/cpus/errata_report.h>
 #include <plat/common/platform.h>
 
 #include "psci_private.h"
diff --git a/plat/arm/board/fvp_r/fvp_r_bl1_main.c b/plat/arm/board/fvp_r/fvp_r_bl1_main.c
index 841a176..252fc31 100644
--- a/plat/arm/board/fvp_r/fvp_r_bl1_main.c
+++ b/plat/arm/board/fvp_r/fvp_r_bl1_main.c
@@ -15,7 +15,7 @@
 #include <common/debug.h>
 #include <drivers/auth/auth_mod.h>
 #include <drivers/console.h>
-#include <lib/cpus/errata_report.h>
+#include <lib/cpus/errata.h>
 #include <lib/utils.h>
 #include <smccc_helpers.h>
 #include <tools_share/uuid.h>
diff --git a/plat/arm/board/fvp_r/platform.mk b/plat/arm/board/fvp_r/platform.mk
index 5dd28b9..f14ea54 100644
--- a/plat/arm/board/fvp_r/platform.mk
+++ b/plat/arm/board/fvp_r/platform.mk
@@ -83,6 +83,7 @@
 				drivers/io/io_storage.c				\
 				drivers/io/io_semihosting.c			\
 				lib/cpus/aarch64/cpu_helpers.S			\
+				lib/cpus/errata_report.c			\
 				lib/fconf/fconf_dyn_cfg_getter.c		\
 				lib/semihosting/semihosting.c			\
 				lib/semihosting/${ARCH}/semihosting_call.S	\
diff --git a/poetry.lock b/poetry.lock
index 58522c9..92b38da 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -13,6 +13,25 @@
 ]
 
 [[package]]
+name = "anytree"
+version = "2.8.0"
+description = "Powerful and Lightweight Python Tree Data Structure.."
+category = "dev"
+optional = false
+python-versions = "*"
+files = [
+    {file = "anytree-2.8.0-py2.py3-none-any.whl", hash = "sha256:14c55ac77492b11532395049a03b773d14c7e30b22aa012e337b1e983de31521"},
+    {file = "anytree-2.8.0.tar.gz", hash = "sha256:3f0f93f355a91bc3e6245319bf4c1d50e3416cc7a35cc1133c1ff38306bbccab"},
+]
+
+[package.dependencies]
+six = ">=1.9.0"
+
+[package.extras]
+dev = ["check-manifest"]
+test = ["coverage"]
+
+[[package]]
 name = "babel"
 version = "2.12.1"
 description = "Internationalization utilities"
@@ -213,14 +232,14 @@
 
 [[package]]
 name = "importlib-metadata"
-version = "6.0.0"
+version = "6.6.0"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"},
-    {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"},
+    {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"},
+    {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"},
 ]
 
 [package.dependencies]
@@ -395,38 +414,38 @@
 
 [[package]]
 name = "packaging"
-version = "23.0"
+version = "23.1"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
-    {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
+    {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
+    {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
 ]
 
 [[package]]
 name = "pip"
-version = "23.0.1"
+version = "23.1.2"
 description = "The PyPA recommended tool for installing Python packages."
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pip-23.0.1-py3-none-any.whl", hash = "sha256:236bcb61156d76c4b8a05821b988c7b8c35bf0da28a4b614e8d6ab5212c25c6f"},
-    {file = "pip-23.0.1.tar.gz", hash = "sha256:cd015ea1bfb0fcef59d8a286c1f8bebcb983f6317719d415dc5351efb7cd7024"},
+    {file = "pip-23.1.2-py3-none-any.whl", hash = "sha256:3ef6ac33239e4027d9a5598a381b9d30880a1477e50039db2eac6e8a8f6d1b18"},
+    {file = "pip-23.1.2.tar.gz", hash = "sha256:0e7c86f486935893c708287b30bd050a36ac827ec7fe5e43fe7cb198dd835fba"},
 ]
 
 [[package]]
 name = "pip-tools"
-version = "6.12.3"
+version = "6.13.0"
 description = "pip-tools keeps your pinned dependencies fresh."
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pip-tools-6.12.3.tar.gz", hash = "sha256:480d44fae6e09fad3f9bd3d0a7e8423088715d10477e8ef0663440db25e3114f"},
-    {file = "pip_tools-6.12.3-py3-none-any.whl", hash = "sha256:8510420f46572b2e26c357541390593d9365eb6edd2d1e7505267910ecaec080"},
+    {file = "pip-tools-6.13.0.tar.gz", hash = "sha256:61d46bd2eb8016ed4a924e196e6e5b0a268cd3babd79e593048720db23522bb1"},
+    {file = "pip_tools-6.13.0-py3-none-any.whl", hash = "sha256:50943f151d87e752abddec8158622c34ad7f292e193836e90e30d87da60b19d9"},
 ]
 
 [package.dependencies]
@@ -441,15 +460,45 @@
 testing = ["flit-core (>=2,<4)", "poetry-core (>=1.0.0)", "pytest (>=7.2.0)", "pytest-rerunfailures", "pytest-xdist"]
 
 [[package]]
+name = "prettytable"
+version = "3.7.0"
+description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "prettytable-3.7.0-py3-none-any.whl", hash = "sha256:f4aaf2ed6e6062a82fd2e6e5289bbbe705ec2788fe401a3a1f62a1cea55526d2"},
+    {file = "prettytable-3.7.0.tar.gz", hash = "sha256:ef8334ee40b7ec721651fc4d37ecc7bb2ef55fde5098d994438f0dfdaa385c0c"},
+]
+
+[package.dependencies]
+wcwidth = "*"
+
+[package.extras]
+tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"]
+
+[[package]]
+name = "pyelftools"
+version = "0.29"
+description = "Library for analyzing ELF files and DWARF debugging information"
+category = "dev"
+optional = false
+python-versions = "*"
+files = [
+    {file = "pyelftools-0.29-py2.py3-none-any.whl", hash = "sha256:519f38cf412f073b2d7393aa4682b0190fa901f7c3fa0bff2b82d537690c7fc1"},
+    {file = "pyelftools-0.29.tar.gz", hash = "sha256:ec761596aafa16e282a31de188737e5485552469ac63b60cfcccf22263fd24ff"},
+]
+
+[[package]]
 name = "pygments"
-version = "2.14.0"
+version = "2.15.1"
 description = "Pygments is a syntax highlighting package written in Python."
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
-    {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
-    {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
+    {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
+    {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
 ]
 
 [package.extras]
@@ -472,14 +521,14 @@
 
 [[package]]
 name = "pytz"
-version = "2022.7.1"
+version = "2023.3"
 description = "World timezone definitions, modern and historical"
 category = "dev"
 optional = false
 python-versions = "*"
 files = [
-    {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"},
-    {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"},
+    {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"},
+    {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"},
 ]
 
 [[package]]
@@ -534,21 +583,21 @@
 
 [[package]]
 name = "requests"
-version = "2.28.2"
+version = "2.30.0"
 description = "Python HTTP for Humans."
 category = "dev"
 optional = false
-python-versions = ">=3.7, <4"
+python-versions = ">=3.7"
 files = [
-    {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"},
-    {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"},
+    {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"},
+    {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"},
 ]
 
 [package.dependencies]
 certifi = ">=2017.4.17"
 charset-normalizer = ">=2,<4"
 idna = ">=2.5,<4"
-urllib3 = ">=1.21.1,<1.27"
+urllib3 = ">=1.21.1,<3"
 
 [package.extras]
 socks = ["PySocks (>=1.5.6,!=1.5.7)"]
@@ -556,14 +605,14 @@
 
 [[package]]
 name = "setuptools"
-version = "67.6.0"
+version = "67.7.2"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "setuptools-67.6.0-py3-none-any.whl", hash = "sha256:b78aaa36f6b90a074c1fa651168723acbf45d14cb1196b6f02c0fd07f17623b2"},
-    {file = "setuptools-67.6.0.tar.gz", hash = "sha256:2ee892cd5f29f3373097f5a814697e397cf3ce313616df0af11231e2ad118077"},
+    {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"},
+    {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"},
 ]
 
 [package.extras]
@@ -572,6 +621,18 @@
 testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
 
 [[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
 name = "snowballstemmer"
 version = "2.2.0"
 description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
@@ -792,20 +853,33 @@
 
 [[package]]
 name = "urllib3"
-version = "1.26.15"
+version = "2.0.2"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.7"
 files = [
-    {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"},
-    {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"},
+    {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"},
+    {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"},
 ]
 
 [package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "wcwidth"
+version = "0.2.6"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+files = [
+    {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
+    {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
+]
 
 [[package]]
 name = "wheel"
@@ -841,4 +915,4 @@
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.8"
-content-hash = "07432d506e3dc69114203b554d82c1489372ce0087d4a430d0380e437afa5714"
+content-hash = "9c25ef33612d10c7caafa551a3cf6a12753167c6400f49cc261fddd18c7eaf6e"
diff --git a/pyproject.toml b/pyproject.toml
index 437290a..44e78d3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,6 +5,12 @@
 authors = ["Arm Ltd."]
 license = "BSD-3-Clause"
 readme = "readme.rst"
+packages = [
+	{ include = "memory", from = "tools/memory"}
+]
+
+[tool.poetry.scripts]
+memory = "memory.memmap:main"
 
 [tool.poetry.dependencies]
 python = "^3.8"
@@ -18,3 +24,9 @@
 
 [tool.poetry.group.ci.dependencies]
 click = "^8.1.3"
+
+[tool.poetry.group.memory.dependencies]
+pyelftools = "^0.29"
+anytree = "^2.8.0"
+click = "^8.1.3"
+prettytable = "^3.5.0"
diff --git a/services/arm_arch_svc/arm_arch_svc_setup.c b/services/arm_arch_svc/arm_arch_svc_setup.c
index 46ccd9e..bb042c7 100644
--- a/services/arm_arch_svc/arm_arch_svc_setup.c
+++ b/services/arm_arch_svc/arm_arch_svc_setup.c
@@ -6,7 +6,7 @@
 
 #include <common/debug.h>
 #include <common/runtime_svc.h>
-#include <lib/cpus/errata_report.h>
+#include <lib/cpus/errata.h>
 #include <lib/cpus/wa_cve_2017_5715.h>
 #include <lib/cpus/wa_cve_2018_3639.h>
 #include <lib/cpus/wa_cve_2022_23960.h>
diff --git a/tools/memory/__init__.py b/tools/memory/__init__.py
new file mode 100644
index 0000000..0b4c8d3
--- /dev/null
+++ b/tools/memory/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
diff --git a/tools/memory/memory/__init__.py b/tools/memory/memory/__init__.py
new file mode 100644
index 0000000..0b4c8d3
--- /dev/null
+++ b/tools/memory/memory/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
diff --git a/tools/memory/memory/buildparser.py b/tools/memory/memory/buildparser.py
new file mode 100755
index 0000000..6f467cd
--- /dev/null
+++ b/tools/memory/memory/buildparser.py
@@ -0,0 +1,56 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import re
+from pathlib import Path
+
+from memory.elfparser import TfaElfParser
+
+
+class TfaBuildParser:
+    """A class for performing analysis on the memory layout of a TF-A build."""
+
+    def __init__(self, path: Path):
+        self._modules = dict()
+        self._path = path
+        self._parse_modules()
+
+    def __getitem__(self, module: str):
+        """Returns an TfaElfParser instance indexed by module."""
+        return self._modules[module]
+
+    def _parse_modules(self):
+        """Parse ELF files in the build path."""
+        for elf_file in self._path.glob("**/*.elf"):
+            module_name = elf_file.name.split("/")[-1].split(".")[0]
+            with open(elf_file, "rb") as file:
+                self._modules[module_name] = TfaElfParser(file)
+
+        if not len(self._modules):
+            raise FileNotFoundError(
+                f"failed to find ELF files in path {self._path}!"
+            )
+
+    @property
+    def symbols(self) -> list:
+        return [
+            (*sym, k) for k, v in self._modules.items() for sym in v.symbols
+        ]
+
+    @staticmethod
+    def filter_symbols(symbols: list, regex: str = None) -> list:
+        """Returns a map of symbols to modules."""
+        regex = r".*" if not regex else regex
+        return sorted(
+            filter(lambda s: re.match(regex, s[0]), symbols),
+            key=lambda s: (-s[1], s[0]),
+            reverse=True,
+        )
+
+    @property
+    def module_names(self):
+        """Returns sorted list of module names."""
+        return sorted(self._modules.keys())
diff --git a/tools/memory/memory/elfparser.py b/tools/memory/memory/elfparser.py
new file mode 100644
index 0000000..3964e6c
--- /dev/null
+++ b/tools/memory/memory/elfparser.py
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+from typing import BinaryIO
+
+from elftools.elf.elffile import ELFFile
+
+
+class TfaElfParser:
+    """A class representing an ELF file built for TF-A.
+
+    Provides a basic interface for reading the symbol table and other
+    attributes of an ELF file. The constructor accepts a file-like object with
+    the contents an ELF file.
+    """
+
+    def __init__(self, elf_file: BinaryIO):
+        self._segments = {}
+        self._memory_layout = {}
+
+        elf = ELFFile(elf_file)
+
+        self._symbols = {
+            sym.name: sym.entry["st_value"]
+            for sym in elf.get_section_by_name(".symtab").iter_symbols()
+        }
+
+    @property
+    def symbols(self):
+        return self._symbols.items()
diff --git a/tools/memory/memory/memmap.py b/tools/memory/memory/memmap.py
new file mode 100755
index 0000000..7057228
--- /dev/null
+++ b/tools/memory/memory/memmap.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+from pathlib import Path
+
+import click
+from memory.buildparser import TfaBuildParser
+from memory.printer import TfaPrettyPrinter
+
+
+@click.command()
+@click.option(
+    "-r",
+    "--root",
+    type=Path,
+    default=None,
+    help="Root containing build output.",
+)
+@click.option(
+    "-p",
+    "--platform",
+    show_default=True,
+    default="fvp",
+    help="The platform targeted for analysis.",
+)
+@click.option(
+    "-b",
+    "--build-type",
+    default="release",
+    help="The target build type.",
+    type=click.Choice(["debug", "release"], case_sensitive=False),
+)
+@click.option(
+    "-s",
+    "--symbols",
+    is_flag=True,
+    show_default=True,
+    default=True,
+    help="Generate a map of important TF symbols.",
+)
+@click.option("-w", "--width", type=int, envvar="COLUMNS")
+@click.option(
+    "-d",
+    is_flag=True,
+    default=False,
+    help="Display numbers in decimal base.",
+)
+def main(
+    root: Path,
+    platform: str,
+    build_type: str,
+    symbols: bool,
+    width: int,
+    d: bool,
+):
+    build_path = root if root else Path("build/", platform, build_type)
+    click.echo(f"build-path: {build_path.resolve()}")
+
+    parser = TfaBuildParser(build_path)
+    printer = TfaPrettyPrinter(columns=width, as_decimal=d)
+
+    if symbols:
+        expr = (
+            r"(.*)(TEXT|BSS|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF"
+            r"|R.M)(.*)(START|END)__$"
+        )
+        printer.print_symbol_table(
+            parser.filter_symbols(parser.symbols, expr), parser.module_names
+        )
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/memory/memory/printer.py b/tools/memory/memory/printer.py
new file mode 100755
index 0000000..11fd7f0
--- /dev/null
+++ b/tools/memory/memory/printer.py
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+
+class TfaPrettyPrinter:
+    """A class for printing the memory layout of ELF files.
+
+    This class provides interfaces for printing various memory layout views of
+    ELF files in a TF-A build. It can be used to understand how the memory is
+    structured and consumed.
+    """
+
+    def __init__(self, columns: int = None, as_decimal: bool = False):
+        self.term_size = columns if columns and columns > 120 else 120
+        self._symbol_map = None
+        self.as_decimal = as_decimal
+
+    def format_args(self, *args, width=10, fmt=None):
+        if not fmt and type(args[0]) is int:
+            fmt = f">{width}x" if not self.as_decimal else f">{width}"
+        return [f"{arg:{fmt}}" if fmt else arg for arg in args]
+
+    @staticmethod
+    def map_elf_symbol(
+        leading: str,
+        section_name: str,
+        rel_pos: int,
+        columns: int,
+        width: int = None,
+        is_edge: bool = False,
+    ):
+        empty_col = "{:{}{}}"
+
+        # Some symbols are longer than the column width, truncate them until
+        # we find a more elegant way to display them!
+        len_over = len(section_name) - width
+        if len_over > 0:
+            section_name = section_name[len_over:-len_over]
+
+        sec_row = f"+{section_name:-^{width-1}}+"
+        sep, fill = ("+", "-") if is_edge else ("|", "")
+
+        sec_row_l = empty_col.format(sep, fill + "<", width) * rel_pos
+        sec_row_r = empty_col.format(sep, fill + ">", width) * (
+            columns - rel_pos - 1
+        )
+
+        return leading + sec_row_l + sec_row + sec_row_r
+
+    def print_symbol_table(
+        self,
+        symbols: list,
+        modules: list,
+        start: int = 11,
+    ):
+        assert len(symbols), "Empty symbol list!"
+        modules = sorted(modules)
+        col_width = int((self.term_size - start) / len(modules))
+
+        num_fmt = "0=#010x" if not self.as_decimal else ">10"
+
+        _symbol_map = [
+            " " * start
+            + "".join(self.format_args(*modules, fmt=f"^{col_width}"))
+        ]
+        last_addr = None
+
+        for i, (name, addr, mod) in enumerate(symbols):
+            # Do not print out an address twice if two symbols overlap,
+            # for example, at the end of one region and start of another.
+            leading = (
+                f"{addr:{num_fmt}}" + " " if addr != last_addr else " " * start
+            )
+
+            _symbol_map.append(
+                self.map_elf_symbol(
+                    leading,
+                    name,
+                    modules.index(mod),
+                    len(modules),
+                    width=col_width,
+                    is_edge=(not i or i == len(symbols) - 1),
+                )
+            )
+
+            last_addr = addr
+
+        self._symbol_map = ["Memory Layout:"]
+        self._symbol_map += list(reversed(_symbol_map))
+        print("\n".join(self._symbol_map))
diff --git a/tools/memory/print_memory_map.py b/tools/memory/print_memory_map.py
deleted file mode 100755
index ef53f7e..0000000
--- a/tools/memory/print_memory_map.py
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (c) 2019-2022, Arm Limited and Contributors. All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-#
-
-import re
-import os
-import sys
-import operator
-
-# List of folder/map to parse
-bl_images = ['bl1', 'bl2', 'bl31']
-
-# List of symbols to search for
-blx_symbols = ['__BL1_RAM_START__', '__BL1_RAM_END__',
-                '__BL2_END__',
-                '__BL31_END__',
-                '__RO_START__', '__RO_END_UNALIGNED__', '__RO_END__',
-                '__TEXT_START__', '__TEXT_END__',
-                '__TEXT_RESIDENT_START__', '__TEXT_RESIDENT_END__',
-                '__RODATA_START__', '__RODATA_END__',
-                '__DATA_START__', '__DATA_END__',
-                '__STACKS_START__', '__STACKS_END__',
-                '__BSS_START__', '__BSS_END__',
-                '__COHERENT_RAM_START__', '__COHERENT_RAM_END__',
-                '__CPU_OPS_START__', '__CPU_OPS_END__',
-                '__FCONF_POPULATOR_START__', '__FCONF_POPULATOR_END__',
-                '__GOT_START__', '__GOT_END__',
-                '__PARSER_LIB_DESCS_START__', '__PARSER_LIB_DESCS_END__',
-                '__PMF_TIMESTAMP_START__', '__PMF_TIMESTAMP_END__',
-                '__PMF_SVC_DESCS_START__', '__PMF_SVC_DESCS_END__',
-                '__RELA_START__', '__RELA_END__',
-                '__RT_SVC_DESCS_START__', '__RT_SVC_DESCS_END__',
-                '__BASE_XLAT_TABLE_START__', '__BASE_XLAT_TABLE_END__',
-                '__XLAT_TABLE_START__', '__XLAT_TABLE_END__',
-               ]
-
-# Regex to extract address from map file
-address_pattern = re.compile(r"\b0x\w*")
-
-# List of found element: [address, symbol, file]
-address_list = []
-
-# Get the directory from command line or use a default one
-inverted_print = True
-if len(sys.argv) >= 2:
-    build_dir = sys.argv[1]
-    if len(sys.argv) >= 3:
-        inverted_print = sys.argv[2] == '0'
-else:
-    build_dir = 'build/fvp/debug'
-
-max_len = max(len(word) for word in blx_symbols) + 2
-if (max_len % 2) != 0:
-    max_len += 1
-
-# Extract all the required symbols from the map files
-for image in bl_images:
-    file_path = os.path.join(build_dir, image, '{}.map'.format(image))
-    if os.path.isfile(file_path):
-        with open (file_path, 'rt') as mapfile:
-            for line in mapfile:
-                for symbol in blx_symbols:
-                    skip_symbol = 0
-                    # Regex to find symbol definition
-                    line_pattern = re.compile(r"\b0x\w*\s*" + symbol + "\s= .")
-                    match = line_pattern.search(line)
-                    if match:
-                        # Extract address from line
-                        match = address_pattern.search(line)
-                        if match:
-                            if '_END__' in symbol:
-                                sym_start = symbol.replace('_END__', '_START__')
-                                if [match.group(0), sym_start, image] in address_list:
-                                    address_list.remove([match.group(0), sym_start, image])
-                                    skip_symbol = 1
-                            if skip_symbol == 0:
-                                address_list.append([match.group(0), symbol, image])
-
-# Sort by address
-address_list.sort(key=operator.itemgetter(0))
-
-# Invert list for lower address at bottom
-if inverted_print:
-    address_list = reversed(address_list)
-
-# Generate memory view
-print(('{:-^%d}' % (max_len * 3 + 20 + 7)).format('Memory Map from: ' + build_dir))
-for address in address_list:
-    if "bl1" in address[2]:
-        print(address[0], ('+{:-^%d}+ |{:^%d}| |{:^%d}|' % (max_len, max_len, max_len)).format(address[1], '', ''))
-    elif "bl2" in address[2]:
-        print(address[0], ('|{:^%d}| +{:-^%d}+ |{:^%d}|' % (max_len, max_len, max_len)).format('', address[1], ''))
-    elif "bl31" in address[2]:
-        print(address[0], ('|{:^%d}| |{:^%d}| +{:-^%d}+' % (max_len, max_len, max_len)).format('', '', address[1]))
-    else:
-        print(address[0], ('|{:^%d}| |{:^%d}| +{:-^%d}+' % (max_len, max_len, max_len)).format('', '', address[1]))
-
-print(('{:^20}{:_^%d}   {:_^%d}   {:_^%d}' % (max_len, max_len, max_len)).format('', '', '', ''))
-print(('{:^20}{:^%d}   {:^%d}   {:^%d}' % (max_len, max_len, max_len)).format('address', 'bl1', 'bl2', 'bl31'))