1#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-3-Clause
3# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
4#
5
6"""
7Script creating a bundle from app binaries and RMM binary
8
9The script prepends the app binaries to the RMM binary, and generates a branch
10instruction at the beginning of the binary file. This way the RMM code can start
11running when the execution reaches the beginning of the bundled binary.
12
13A bundled RMM binary has the following structure:
14```
15    +------------------------------+                          -+
16    |     BL rmm_bin_offset        | Generated by this script  |
17    |                              |                           |
18    | Rest of the header unchanged |                           |
19    |..............................|                           |
20    |                              |                           + app_bin_file_1
21    |      App binary content      |                           |
22    |                              |                           |
23    +------------------------------+                          -+
24    |     Unchanged bin header     |                           |
25    |..............................|                           |
26    |                              |                           + app_bin_file_2
27    |      App binary content      |                           |
28    |                              |                           |
29    +------------------------------+                          -+
30    |                              |                           |
31                  ...                                         ...
32    |                              |                           |
33    +------------------------------+                          -+
34    |     Unchanged bin header     |                           |
35    |..............................|                           |
36    |                              |                           + app_bin_file_n
37    |      App binary content      |                           |
38    |                              |                           |
39    +------------------------------+                          -+
40    |   Pad to RMM_BIN_ALIGNMENT   |                           |
41    |       (May be missing)       |                           |
42    +------------------------------+                          -+
43    |                              |                           |
44    |      RMM binary content      |                           +rmm_bin
45    |                              |                           |
46    +------------------------------+                          -+
47```
48"""
49
50from argparse import ArgumentParser
51import logging
52import struct
53import sys
54
55logger = None
56
57RMM_BIN_ALIGNMENT = 64 * 1024
58
59
60def initial_branch_instruction(offset):
61    """Generate the initial branch instruction to jump to RMM text"""
62    assert offset > 0
63    assert offset % 4 == 0
64    imm = offset // 4
65    assert imm < (1 << 26)  # imm can be at most 25 bits
66    template = 0x94000000
67    # Use struct to make sure that the result is a 4 byte integer in
68    # little-endian byte order
69    return struct.pack("<I", template | imm)
70
71
72def main():
73    """Main function of the script"""
74
75    parser = ArgumentParser(
76        description="Create a bundle from the app and RMM binaries."
77    )
78    parser.add_argument(
79        "app_bin_files",
80        metavar="APP_BIN_FILE",
81        type=str,
82        nargs="+",
83        help="input application data file(s) for bin generation",
84    )
85    parser.add_argument(
86        "--out-bin",
87        metavar="FILE",
88        type=str,
89        required=True,
90        help="the output bin file generated by gen_app_bin.py",
91    )
92    parser.add_argument(
93        "--rmm-bin",
94        metavar="FILE",
95        type=str,
96        required=True,
97        help="the RMM bin input file for bin generation",
98    )
99    parser.add_argument(
100        "--log-file-name",
101        metavar="FILE",
102        type=str,
103        required=False,
104        default="",
105        help="write logs to 'FILE' as well",
106    )
107
108    args = parser.parse_args()
109
110    global logger
111    logger = logging.getLogger()
112    logger.setLevel(logging.DEBUG)
113    fmt = logging.Formatter("%(levelname)s: %(message)s")
114    console_hdl = logging.StreamHandler(sys.stdout)
115    console_hdl.setFormatter(fmt)
116    logger.addHandler(console_hdl)
117
118    if args.log_file_name:
119        file_hdl = logging.FileHandler(args.log_file_name, mode="w")
120        file_hdl.setFormatter(fmt)
121        logger.addHandler(file_hdl)
122
123    apps_size = 0
124    app_bin_contents = []
125
126    # Collect the contents of the app bin files and concatenate them in a list.
127    for app_bin_file_name in args.app_bin_files:
128        with open(app_bin_file_name, "rb") as app_bin_file:
129            app_bin_content = app_bin_file.read()
130            apps_size += len(app_bin_content)
131            app_bin_contents.append(app_bin_content)
132
133    rmm_bin_padding = 0
134    if (apps_size % RMM_BIN_ALIGNMENT) != 0:
135        rmm_bin_padding = RMM_BIN_ALIGNMENT - (apps_size % RMM_BIN_ALIGNMENT)
136
137    rmm_bin_offset = apps_size + rmm_bin_padding
138
139    # Create the bundled bin file
140    with open(args.out_bin, "wb") as out_file:
141        # Write the starting branch instruction
142        out_file.write(initial_branch_instruction(rmm_bin_offset))
143        # for the first entry, the Initial branch instruction is added in place
144        # the first 4 bytes of the padding in the app header.
145        start_offset = 4
146        for app_bin_content in app_bin_contents:
147            out_file.write(app_bin_content[start_offset:])
148            # For the rest of the files, write the full header
149            start_offset = 0
150
151        # Add padding so that the RMM bin start is aligned at RMM_BIN_ALIGNMENT
152        out_file.write(bytearray(rmm_bin_padding))
153
154        # Add the RMM bin file to the bundle
155        with open(args.rmm_bin, "rb") as rmm_bin_file:
156            out_file.write(rmm_bin_file.read())
157
158    logger.info(
159        f"{args.out_bin} was successfully created. Added {len(args.app_bin_files)} app(s)."
160    )
161    logger.info(
162        f"The offset of the RMM core is {rmm_bin_offset} (0x{rmm_bin_offset:x}) bytes from start of packaged bin."
163    )
164
165
166if __name__ == "__main__":
167    main()
168