1#!/usr/bin/env python3
2#
3# Copyright (c) 2018 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8Script to generate a linker script organizing application memory partitions
9
10Applications may declare build-time memory domain partitions with
11K_APPMEM_PARTITION_DEFINE, and assign globals to them using K_APP_DMEM
12or K_APP_BMEM macros. For each of these partitions, we need to
13route all their data into appropriately-sized memory areas which meet the
14size/alignment constraints of the memory protection hardware.
15
16This linker script is created very early in the build process, before
17the build attempts to link the kernel binary, as the linker script this
18tool generates is a necessary pre-condition for kernel linking. We extract
19the set of memory partitions to generate by looking for variables which
20have been assigned to input sections that follow a defined naming convention.
21We also allow entire libraries to be pulled in to assign their globals
22to a particular memory partition via command line directives.
23
24This script takes as inputs:
25
26- The base directory to look for compiled objects
27- key/value pairs mapping static library files to what partitions their globals
28  should end up in.
29
30The output is a linker script fragment containing the definition of the
31app shared memory section, which is further divided, for each partition
32found, into data and BSS for each partition.
33"""
34
35import sys
36import argparse
37import json
38import os
39import re
40from collections import OrderedDict
41from elftools.elf.elffile import ELFFile
42from elftools.elf.sections import SymbolTableSection
43import elftools.common.exceptions
44
45SZ = 'size'
46SRC = 'sources'
47LIB = 'libraries'
48
49# This script will create sections and linker variables to place the
50# application shared memory partitions.
51# these are later read by the macros defined in app_memdomain.h for
52# initialization purpose when USERSPACE is enabled.
53data_template = """
54		/* Auto generated code do not modify */
55		SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start);
56		z_data_smem_{0}_part_start = .;
57		KEEP(*(data_smem_{0}_data*))
58"""
59
60library_data_template = """
61		*{0}:*(.data .data.* .sdata .sdata.*)
62"""
63
64bss_template = """
65		z_data_smem_{0}_bss_start = .;
66		KEEP(*(data_smem_{0}_bss*))
67"""
68
69library_bss_template = """
70		*{0}:*(.bss .bss.* .sbss .sbss.* COMMON COMMON.*)
71"""
72
73footer_template = """
74		z_data_smem_{0}_bss_end = .;
75		SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start);
76		z_data_smem_{0}_part_end = .;
77"""
78
79linker_start_seq = """
80	SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,)
81	{{
82		APP_SHARED_ALIGN;
83		_app_smem{0}_start = .;
84"""
85
86linker_end_seq = """
87		APP_SHARED_ALIGN;
88		_app_smem{0}_end = .;
89	}} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
90"""
91
92empty_app_smem = """
93	SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,)
94	{{
95		_app_smem{0}_start = .;
96		_app_smem{0}_end = .;
97	}} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
98"""
99
100size_cal_string = """
101	z_data_smem_{0}_part_size = z_data_smem_{0}_part_end - z_data_smem_{0}_part_start;
102	z_data_smem_{0}_bss_size = z_data_smem_{0}_bss_end - z_data_smem_{0}_bss_start;
103"""
104
105section_regex = re.compile(r'data_smem_([A-Za-z0-9_]*)_(data|bss)*')
106
107elf_part_size_regex = re.compile(r'z_data_smem_(.*)_part_size')
108
109def find_obj_file_partitions(filename, partitions):
110    with open(filename, 'rb') as f:
111        try:
112            full_lib = ELFFile(f)
113        except elftools.common.exceptions.ELFError as e:
114            exit(f"Error: {filename}: {e}")
115
116        if not full_lib:
117            sys.exit("Error parsing file: " + filename)
118
119        sections = [x for x in full_lib.iter_sections()]
120        for section in sections:
121            m = section_regex.match(section.name)
122            if not m:
123                continue
124
125            partition_name = m.groups()[0]
126            if partition_name not in partitions:
127                partitions[partition_name] = {SZ: section.header.sh_size}
128
129                if args.verbose:
130                    partitions[partition_name][SRC] = filename
131
132            else:
133                partitions[partition_name][SZ] += section.header.sh_size
134
135
136    return partitions
137
138
139def parse_obj_files(partitions):
140    # Iterate over all object files to find partitions
141    for dirpath, _, files in os.walk(args.directory):
142        for filename in files:
143            if re.match(r".*\.obj$", filename):
144                fullname = os.path.join(dirpath, filename)
145                fsize = os.path.getsize(fullname)
146                if fsize != 0:
147                    find_obj_file_partitions(fullname, partitions)
148
149
150def parse_compile_command_file(partitions):
151    # Iterate over all entries to find object files.
152    # Thereafter process each object file to find partitions
153    object_pattern = re.compile(r'-o\s+(\S*)')
154    with open(args.compile_commands_file, 'rb') as f:
155        commands = json.load(f)
156        for command in commands:
157            build_dir = command.get('directory')
158            compile_command = command.get('command')
159            compile_arg = object_pattern.search(compile_command)
160            obj_file = None if compile_arg is None else compile_arg.group(1)
161            if obj_file:
162                fullname = os.path.join(build_dir, obj_file)
163                # Because of issue #40635, then not all objects referenced by
164                # the compile_commands.json file may be available, therefore
165                # only include existing files.
166                if os.path.exists(fullname):
167                    find_obj_file_partitions(fullname, partitions)
168
169
170def parse_elf_file(partitions):
171    with open(args.elf, 'rb') as f:
172        try:
173            elffile = ELFFile(f)
174        except elftools.common.exceptions.ELFError as e:
175            exit(f"Error: {args.elf}: {e}")
176
177        symbol_tbls = [s for s in elffile.iter_sections()
178                       if isinstance(s, SymbolTableSection)]
179
180        for section in symbol_tbls:
181            for symbol in section.iter_symbols():
182                if symbol['st_shndx'] != "SHN_ABS":
183                    continue
184
185                x = elf_part_size_regex.match(symbol.name)
186                if not x:
187                    continue
188
189                partition_name = x.groups()[0]
190                size = symbol['st_value']
191                if partition_name not in partitions:
192                    partitions[partition_name] = {SZ: size}
193
194                    if args.verbose:
195                        partitions[partition_name][SRC] = args.elf
196
197                else:
198                    partitions[partition_name][SZ] += size
199
200
201def generate_final_linker(linker_file, partitions, lnkr_sect=""):
202    string = ""
203
204    if len(partitions) > 0:
205        string = linker_start_seq.format(lnkr_sect, lnkr_sect.upper())
206        size_string = ''
207        for partition, item in partitions.items():
208            string += data_template.format(partition)
209            if LIB in item:
210                for lib in item[LIB]:
211                    string += library_data_template.format(lib)
212            string += bss_template.format(partition, lnkr_sect)
213            if LIB in item:
214                for lib in item[LIB]:
215                    string += library_bss_template.format(lib)
216            string += footer_template.format(partition)
217            size_string += size_cal_string.format(partition)
218
219        string += linker_end_seq.format(lnkr_sect)
220        string += size_string
221    else:
222        string = empty_app_smem.format(lnkr_sect, lnkr_sect.upper())
223
224    with open(linker_file, "w") as fw:
225        fw.write(string)
226
227
228def parse_args():
229    global args
230    parser = argparse.ArgumentParser(
231        description=__doc__,
232        formatter_class=argparse.RawDescriptionHelpFormatter)
233    parser.add_argument("-d", "--directory", required=False, default=None,
234                        help="Root build directory")
235    parser.add_argument("-e", "--elf", required=False, default=None,
236                        help="ELF file")
237    parser.add_argument("-f", "--compile-commands-file", required=False,
238                        default=None, help="CMake compile commands file")
239    parser.add_argument("-o", "--output", required=False,
240                        help="Output ld file")
241    parser.add_argument("-v", "--verbose", action="count", default=0,
242                        help="Verbose Output")
243    parser.add_argument("-l", "--library", nargs=2, action="append", default=[],
244                        metavar=("LIBRARY", "PARTITION"),
245                        help="Include globals for a particular library or object filename into a designated partition")
246    parser.add_argument("--pinoutput", required=False,
247                        help="Output ld file for pinned sections")
248    parser.add_argument("--pinpartitions", action="store", required=False, default="",
249                        help="Comma separated names of partitions to be pinned in physical memory")
250
251    args = parser.parse_args()
252
253
254def main():
255    parse_args()
256    partitions = {}
257
258    if args.directory is not None:
259        parse_obj_files(partitions)
260    if args.compile_commands_file is not None:
261        parse_compile_command_file(partitions)
262    elif args.elf is not None:
263        parse_elf_file(partitions)
264    else:
265        return
266
267    for lib, ptn in args.library:
268        if ptn not in partitions:
269            partitions[ptn] = {}
270
271        if LIB not in partitions[ptn]:
272            partitions[ptn][LIB] = [lib]
273        else:
274            partitions[ptn][LIB].append(lib)
275
276    if args.pinoutput:
277        pin_part_names = args.pinpartitions.split(',')
278
279        generic_partitions = {key: value for key, value in partitions.items()
280                              if key not in pin_part_names}
281        pinned_partitions = {key: value for key, value in partitions.items()
282                             if key in pin_part_names}
283    else:
284        generic_partitions = partitions
285
286    # Sample partitions.items() list before sorting:
287    #   [ ('part1', {'size': 64}), ('part3', {'size': 64}, ...
288    #     ('part0', {'size': 334}) ]
289    decreasing_tuples = sorted(generic_partitions.items(),
290                           key=lambda x: (x[1][SZ], x[0]), reverse=True)
291
292    partsorted = OrderedDict(decreasing_tuples)
293
294    generate_final_linker(args.output, partsorted)
295    if args.verbose:
296        print("Partitions retrieved:")
297        for key in partsorted:
298            print("    {0}: size {1}: {2}".format(key,
299                                                  partsorted[key][SZ],
300                                                  partsorted[key][SRC]))
301
302    if args.pinoutput:
303        decreasing_tuples = sorted(pinned_partitions.items(),
304                                   key=lambda x: (x[1][SZ], x[0]), reverse=True)
305
306        partsorted = OrderedDict(decreasing_tuples)
307
308        generate_final_linker(args.pinoutput, partsorted, lnkr_sect="_pinned")
309        if args.verbose:
310            print("Pinned partitions retrieved:")
311            for key in partsorted:
312                print("    {0}: size {1}: {2}".format(key,
313                                                    partsorted[key][SZ],
314                                                    partsorted[key][SRC]))
315
316
317if __name__ == '__main__':
318    main()
319