feat(memmap): add tabular memory use data

Add support for tabulating static memory consumption data from ELF
binaries. This relies on static symbols, defined in the linker files,
that provide information about the memory ranges.

Change-Id: Ie19cd2b80a7b591607640feeb84c63266963ea4d
Signed-off-by: Harrison Mutai <harrison.mutai@arm.com>
diff --git a/changelog.yaml b/changelog.yaml
index 9114dad..39dbf3b 100644
--- a/changelog.yaml
+++ b/changelog.yaml
@@ -1269,6 +1269,9 @@
       - title: Certificate Creation Tool
         scope: cert-create
 
+      - title: Memory Mapping Tool
+        scope: memmap
+
         deprecated:
           - cert_create
 
diff --git a/docs/tools/memory-layout-tool.rst b/docs/tools/memory-layout-tool.rst
index ce14dab..ff5188e 100644
--- a/docs/tools/memory-layout-tool.rst
+++ b/docs/tools/memory-layout-tool.rst
@@ -113,6 +113,40 @@
 
     poetry run memory --help
 
+Memory Footprint
+~~~~~~~~~~~~~~~~
+
+The tool enables users to view static memory consumption. When the options
+``-f``, or ``--footprint`` are provided, the script analyses the ELF binaries in
+the build path to generate a table (per memory type), showing memory allocation
+and usage. This is the default output generated by the tool.
+
+.. code:: shell
+
+    $ poetry run memory -f
+    build-path: build/fvp/release
+    +----------------------------------------------------------------------------+
+    |                         Memory Usage (bytes) [RAM]                         |
+    +-----------+------------+------------+------------+------------+------------+
+    | Component |   Start    |   Limit    |    Size    |    Free    |   Total    |
+    +-----------+------------+------------+------------+------------+------------+
+    |    BL1    |    4034000 |    4040000 |       7000 |       5000 |       c000 |
+    |    BL2    |    4021000 |    4034000 |       d000 |       6000 |      13000 |
+    |    BL2U   |    4021000 |    4034000 |       a000 |       9000 |      13000 |
+    |    BL31   |    4003000 |    4040000 |      1e000 |      1f000 |      3d000 |
+    +-----------+------------+------------+------------+------------+------------+
+
+    +----------------------------------------------------------------------------+
+    |                         Memory Usage (bytes) [ROM]                         |
+    +-----------+------------+------------+------------+------------+------------+
+    | Component |   Start    |   Limit    |    Size    |    Free    |   Total    |
+    +-----------+------------+------------+------------+------------+------------+
+    |    BL1    |          0 |    4000000 |       5df0 |    3ffa210 |    4000000 |
+    +-----------+------------+------------+------------+------------+------------+
+
+The script relies on symbols in the symbol table to determine the start, end,
+and limit addresses of each bootloader stage.
+
 --------------
 
 *Copyright (c) 2023, Arm Limited. All rights reserved.*
diff --git a/tools/memory/memory/buildparser.py b/tools/memory/memory/buildparser.py
index 6f467cd..0e3beaa 100755
--- a/tools/memory/memory/buildparser.py
+++ b/tools/memory/memory/buildparser.py
@@ -50,6 +50,15 @@
             reverse=True,
         )
 
+    def get_mem_usage_dict(self) -> dict:
+        """Returns map of memory usage per memory type for each module."""
+        mem_map = {}
+        for k, v in self._modules.items():
+            mod_mem_map = v.get_elf_memory_layout()
+            if len(mod_mem_map):
+                mem_map[k] = mod_mem_map
+        return mem_map
+
     @property
     def module_names(self):
         """Returns sorted list of module names."""
diff --git a/tools/memory/memory/elfparser.py b/tools/memory/memory/elfparser.py
index 3964e6c..b61f328 100644
--- a/tools/memory/memory/elfparser.py
+++ b/tools/memory/memory/elfparser.py
@@ -4,6 +4,7 @@
 # SPDX-License-Identifier: BSD-3-Clause
 #
 
+import re
 from typing import BinaryIO
 
 from elftools.elf.elffile import ELFFile
@@ -28,6 +29,46 @@
             for sym in elf.get_section_by_name(".symtab").iter_symbols()
         }
 
+        self._memory_layout = self.get_memory_layout_from_symbols()
+
     @property
     def symbols(self):
         return self._symbols.items()
+
+    def get_memory_layout_from_symbols(self, expr=None) -> dict:
+        """Retrieve information about the memory configuration from the symbol
+        table.
+        """
+        assert len(self._symbols), "Symbol table is empty!"
+
+        expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)" if not expr else expr
+        region_symbols = filter(lambda s: re.match(expr, s), self._symbols)
+        memory_layout = {}
+
+        for symbol in region_symbols:
+            region, _, attr = tuple(symbol.lower().strip("__").split("_"))
+            if region not in memory_layout:
+                memory_layout[region] = {}
+
+            # Retrieve the value of the symbol using the symbol as the key.
+            memory_layout[region][attr] = self._symbols[symbol]
+
+        return memory_layout
+
+    def get_elf_memory_layout(self):
+        """Get the total memory consumed by this module from the memory
+        configuration.
+            {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
+        """
+        mem_dict = {}
+
+        for mem, attrs in self._memory_layout.items():
+            limit = attrs["start"] + attrs["length"]
+            mem_dict[mem] = {
+                "start": attrs["start"],
+                "limit": limit,
+                "size": attrs["end"] - attrs["start"],
+                "free": limit - attrs["end"],
+                "total": attrs["length"],
+            }
+        return mem_dict
diff --git a/tools/memory/memory/memmap.py b/tools/memory/memory/memmap.py
index 7057228..e6b66f9 100755
--- a/tools/memory/memory/memmap.py
+++ b/tools/memory/memory/memmap.py
@@ -36,11 +36,16 @@
     type=click.Choice(["debug", "release"], case_sensitive=False),
 )
 @click.option(
+    "-f",
+    "--footprint",
+    is_flag=True,
+    show_default=True,
+    help="Generate a high level view of memory usage by memory types.",
+)
+@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")
@@ -54,6 +59,7 @@
     root: Path,
     platform: str,
     build_type: str,
+    footprint: bool,
     symbols: bool,
     width: int,
     d: bool,
@@ -64,6 +70,9 @@
     parser = TfaBuildParser(build_path)
     printer = TfaPrettyPrinter(columns=width, as_decimal=d)
 
+    if footprint or not symbols:
+        printer.print_footprint(parser.get_mem_usage_dict())
+
     if symbols:
         expr = (
             r"(.*)(TEXT|BSS|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF"
diff --git a/tools/memory/memory/printer.py b/tools/memory/memory/printer.py
index 11fd7f0..c6019c2 100755
--- a/tools/memory/memory/printer.py
+++ b/tools/memory/memory/printer.py
@@ -4,6 +4,8 @@
 # SPDX-License-Identifier: BSD-3-Clause
 #
 
+from prettytable import PrettyTable
+
 
 class TfaPrettyPrinter:
     """A class for printing the memory layout of ELF files.
@@ -15,6 +17,7 @@
 
     def __init__(self, columns: int = None, as_decimal: bool = False):
         self.term_size = columns if columns and columns > 120 else 120
+        self._footprint = None
         self._symbol_map = None
         self.as_decimal = as_decimal
 
@@ -50,6 +53,37 @@
 
         return leading + sec_row_l + sec_row + sec_row_r
 
+    def print_footprint(
+        self, app_mem_usage: dict, sort_key: str = None, fields: list = None
+    ):
+        assert len(app_mem_usage), "Empty memory layout dictionary!"
+        if not fields:
+            fields = ["Component", "Start", "Limit", "Size", "Free", "Total"]
+
+        sort_key = fields[0] if not sort_key else sort_key
+
+        # Iterate through all the memory types, create a table for each
+        # type, rows represent a single module.
+        for mem in sorted(set(k for _, v in app_mem_usage.items() for k in v)):
+            table = PrettyTable(
+                sortby=sort_key,
+                title=f"Memory Usage (bytes) [{mem.upper()}]",
+                field_names=fields,
+            )
+
+            for mod, vals in app_mem_usage.items():
+                if mem in vals.keys():
+                    val = vals[mem]
+                    table.add_row(
+                        [
+                            mod.upper(),
+                            *self.format_args(
+                                *[val[k.lower()] for k in fields[1:]]
+                            ),
+                        ]
+                    )
+            print(table, "\n")
+
     def print_symbol_table(
         self,
         symbols: list,