1#!/usr/bin/env python3
2#
3# Copyright (c) 2018 Intel Corporation.
4#
5# SPDX-License-Identifier: Apache-2.0
6#
7
8"""
9This script will relocate .text, .rodata, .data and .bss sections from required files
10and places it in the required memory region. This memory region and file
11are given to this python script in the form of a string.
12
13Example of such a string would be::
14
15   SRAM2:/home/xyz/zephyr/samples/hello_world/src/main.c,\
16   SRAM1:/home/xyz/zephyr/samples/hello_world/src/main2.c
17
18To invoke this script::
19
20   python3 gen_relocate_app.py -i input_string -o generated_linker -c generated_code
21
22Configuration that needs to be sent to the python script.
23
24- If the memory is like SRAM1/SRAM2/CCD/AON then place full object in
25  the sections
26- If the memory type is appended with _DATA / _TEXT/ _RODATA/ _BSS only the
27  selected memory is placed in the required memory region. Others are
28  ignored.
29
30Multiple regions can be appended together like SRAM2_DATA_BSS
31this will place data and bss inside SRAM2.
32"""
33
34
35import sys
36import argparse
37import os
38import glob
39import warnings
40from elftools.elf.elffile import ELFFile
41
42# This script will create linker comands for text,rodata data, bss section relocation
43
44PRINT_TEMPLATE = """
45                KEEP(*({0}))
46"""
47
48SECTION_LOAD_MEMORY_SEQ = """
49        __{0}_{1}_rom_start = LOADADDR(_{2}_{3}_SECTION_NAME);
50"""
51
52LOAD_ADDRESS_LOCATION_FLASH = """
53#ifdef CONFIG_XIP
54GROUP_DATA_LINK_IN({0}, FLASH)
55#else
56GROUP_DATA_LINK_IN({0}, {0})
57#endif
58"""
59LOAD_ADDRESS_LOCATION_BSS = "GROUP_LINK_IN({0})"
60
61MPU_RO_REGION_START = """
62
63     _{0}_mpu_ro_region_start = ORIGIN({1});
64
65"""
66
67MPU_RO_REGION_END = """
68
69    _{0}_mpu_ro_region_end = .;
70
71"""
72
73# generic section creation format
74LINKER_SECTION_SEQ = """
75
76/* Linker section for memory region {2} for  {3} section  */
77
78	SECTION_PROLOGUE(_{2}_{3}_SECTION_NAME,,)
79        {{
80                . = ALIGN(4);
81                {4}
82                . = ALIGN(4);
83	}} {5}
84        __{0}_{1}_end = .;
85        __{0}_{1}_start = ADDR(_{2}_{3}_SECTION_NAME);
86        __{0}_{1}_size = SIZEOF(_{2}_{3}_SECTION_NAME);
87"""
88
89LINKER_SECTION_SEQ_MPU = """
90
91/* Linker section for memory region {2} for {3} section  */
92
93	SECTION_PROLOGUE(_{2}_{3}_SECTION_NAME,,)
94        {{
95                __{0}_{1}_start = .;
96                {4}
97#if {6}
98                . = ALIGN({6});
99#else
100                MPU_ALIGN(__{0}_{1}_size);
101#endif
102                __{0}_{1}_end = .;
103	}} {5}
104        __{0}_{1}_size = __{0}_{1}_end - __{0}_{1}_start;
105"""
106
107SOURCE_CODE_INCLUDES = """
108/* Auto generated code. Do not modify.*/
109#include <zephyr.h>
110#include <linker/linker-defs.h>
111#include <kernel_structs.h>
112#include <string.h>
113"""
114
115EXTERN_LINKER_VAR_DECLARATION = """
116extern char __{0}_{1}_start[];
117extern char __{0}_{1}_rom_start[];
118extern char __{0}_{1}_size[];
119"""
120
121
122DATA_COPY_FUNCTION = """
123void data_copy_xip_relocation(void)
124{{
125{0}
126}}
127"""
128
129BSS_ZEROING_FUNCTION = """
130void bss_zeroing_relocation(void)
131{{
132{0}
133}}
134"""
135
136MEMCPY_TEMPLATE = """
137	(void)memcpy(&__{0}_{1}_start, &__{0}_{1}_rom_start,
138		     (uint32_t) &__{0}_{1}_size);
139
140"""
141
142MEMSET_TEMPLATE = """
143 	(void)memset(&__{0}_bss_start, 0,
144		     (uint32_t) &__{0}_bss_size);
145"""
146
147
148def find_sections(filename, full_list_of_sections):
149    with open(filename, 'rb') as obj_file_desc:
150        full_lib = ELFFile(obj_file_desc)
151        if not full_lib:
152            sys.exit("Error parsing file: " + filename)
153
154        sections = [x for x in full_lib.iter_sections()]
155
156        for section in sections:
157
158            if ".text." in section.name:
159                full_list_of_sections["text"].append(section.name)
160
161            if ".rodata." in section.name:
162                full_list_of_sections["rodata"].append(section.name)
163
164            if ".data." in section.name:
165                full_list_of_sections["data"].append(section.name)
166
167            if ".bss." in section.name:
168                full_list_of_sections["bss"].append(section.name)
169
170            # Common variables will be placed in the .bss section
171            # only after linking in the final executable. This "if" finds
172            # common symbols and warns the user of the problem.
173            # The solution to which is simply assigning a 0 to
174            # bss variable and it will go to the required place.
175            if ".symtab" in section.name:
176                symbols = [x for x in section.iter_symbols()]
177                for symbol in symbols:
178                    if symbol.entry["st_shndx"] == 'SHN_COMMON':
179                        warnings.warn("Common variable found. Move "+
180                                      symbol.name + " to bss by assigning it to 0/NULL")
181
182    return full_list_of_sections
183
184
185def assign_to_correct_mem_region(memory_type,
186                                 full_list_of_sections, complete_list_of_sections):
187    all_regions = False
188    iteration_sections = {"text": False, "rodata": False, "data": False, "bss": False}
189    if "_TEXT" in memory_type:
190        iteration_sections["text"] = True
191        memory_type = memory_type.replace("_TEXT", "")
192    if "_RODATA" in memory_type:
193        iteration_sections["rodata"] = True
194        memory_type = memory_type.replace("_RODATA", "")
195    if "_DATA" in memory_type:
196        iteration_sections["data"] = True
197        memory_type = memory_type.replace("_DATA", "")
198    if "_BSS" in memory_type:
199        iteration_sections["bss"] = True
200        memory_type = memory_type.replace("_BSS", "")
201    if not (iteration_sections["data"] or iteration_sections["bss"] or
202            iteration_sections["text"] or iteration_sections["rodata"]):
203        all_regions = True
204
205    pos = memory_type.find('_')
206    if pos in range(len(memory_type)):
207        align_size = int(memory_type[pos+1:])
208        memory_type = memory_type[:pos]
209        mpu_align[memory_type] = align_size
210
211    if memory_type in complete_list_of_sections:
212        for iter_sec in ["text", "rodata", "data", "bss"]:
213            if ((iteration_sections[iter_sec] or all_regions) and
214                    full_list_of_sections[iter_sec] != []):
215                complete_list_of_sections[memory_type][iter_sec] += (
216                    full_list_of_sections[iter_sec])
217    else:
218        # new memory type was found. in which case just assign the
219        # full_list_of_sections to the memorytype dict
220        tmp_list = {"text": [], "rodata": [], "data": [], "bss": []}
221        for iter_sec in ["text", "rodata", "data", "bss"]:
222            if ((iteration_sections[iter_sec] or all_regions) and
223                    full_list_of_sections[iter_sec] != []):
224                tmp_list[iter_sec] = full_list_of_sections[iter_sec]
225
226        complete_list_of_sections[memory_type] = tmp_list
227
228    return complete_list_of_sections
229
230
231def print_linker_sections(list_sections):
232    print_string = ''
233    for section in sorted(list_sections):
234        print_string += PRINT_TEMPLATE.format(section)
235    return print_string
236
237
238def string_create_helper(region, memory_type,
239                         full_list_of_sections, load_address_in_flash):
240    linker_string = ''
241    if load_address_in_flash:
242        load_address_string = LOAD_ADDRESS_LOCATION_FLASH.format(memory_type)
243    else:
244        load_address_string = LOAD_ADDRESS_LOCATION_BSS.format(memory_type)
245    if full_list_of_sections[region]:
246        # Create a complete list of funcs/ variables that goes in for this
247        # memory type
248        tmp = print_linker_sections(full_list_of_sections[region])
249        if memory_type == 'SRAM' and region in {'data', 'bss'}:
250            linker_string += tmp
251        else:
252            if memory_type != 'SRAM' and region == 'rodata':
253                align_size = 0
254                if memory_type in mpu_align.keys():
255                    align_size = mpu_align[memory_type]
256
257                linker_string += LINKER_SECTION_SEQ_MPU.format(memory_type.lower(), region, memory_type.upper(),
258                                                               region.upper(), tmp, load_address_string, align_size)
259            else:
260                if memory_type == 'SRAM' and region == 'text':
261                    align_size = 0
262                    linker_string += LINKER_SECTION_SEQ_MPU.format(memory_type.lower(), region, memory_type.upper(),
263                                                                   region.upper(), tmp, load_address_string, align_size)
264                else:
265                    linker_string += LINKER_SECTION_SEQ.format(memory_type.lower(), region, memory_type.upper(),
266                                                               region.upper(), tmp, load_address_string)
267            if load_address_in_flash:
268                linker_string += SECTION_LOAD_MEMORY_SEQ.format(memory_type.lower(), region, memory_type.upper(),
269                                                                region.upper())
270    return linker_string
271
272
273def generate_linker_script(linker_file, sram_data_linker_file, sram_bss_linker_file, complete_list_of_sections):
274    gen_string = ''
275    gen_string_sram_data = ''
276    gen_string_sram_bss = ''
277
278    for memory_type, full_list_of_sections in \
279            sorted(complete_list_of_sections.items()):
280
281        if memory_type != "SRAM":
282            gen_string += MPU_RO_REGION_START.format(memory_type.lower(), memory_type.upper())
283        gen_string += string_create_helper("text", memory_type, full_list_of_sections, 1)
284        gen_string += string_create_helper("rodata", memory_type, full_list_of_sections, 1)
285        if memory_type != "SRAM":
286            gen_string += MPU_RO_REGION_END.format(memory_type.lower())
287
288        if memory_type == 'SRAM':
289            gen_string_sram_data += string_create_helper("data", memory_type, full_list_of_sections, 1)
290            gen_string_sram_bss += string_create_helper("bss", memory_type, full_list_of_sections, 0)
291        else:
292            gen_string += string_create_helper("data", memory_type, full_list_of_sections, 1)
293            gen_string += string_create_helper("bss", memory_type, full_list_of_sections, 0)
294
295    # finally writing to the linker file
296    with open(linker_file, "w") as file_desc:
297        file_desc.write(gen_string)
298
299    with open(sram_data_linker_file, "w") as file_desc:
300        file_desc.write(gen_string_sram_data)
301
302    with open(sram_bss_linker_file, "w") as file_desc:
303        file_desc.write(gen_string_sram_bss)
304
305
306def generate_memcpy_code(memory_type, full_list_of_sections, code_generation):
307    all_sections = True
308    generate_section = {"text": False, "rodata": False, "data": False, "bss": False}
309    for section_name in ["_TEXT", "_RODATA", "_DATA", "_BSS"]:
310        if section_name in memory_type:
311            generate_section[section_name.lower()[1:]] = True
312            memory_type = memory_type.replace(section_name, "")
313            all_sections = False
314
315    if all_sections:
316        generate_section["text"] = True
317        generate_section["rodata"] = True
318        generate_section["data"] = True
319        generate_section["bss"] = True
320
321    # add all the regions that needs to be copied on boot up
322    for mtype in ["text", "rodata", "data"]:
323        if memory_type == "SRAM" and mtype == "data":
324            continue
325
326        if full_list_of_sections[mtype] and generate_section[mtype]:
327            code_generation["copy_code"] += MEMCPY_TEMPLATE.format(memory_type.lower(), mtype)
328            code_generation["extern"] += EXTERN_LINKER_VAR_DECLARATION.format(
329                memory_type.lower(), mtype)
330
331    # add for all the bss data that needs to be zeored on boot up
332    if full_list_of_sections["bss"] and generate_section["bss"] and memory_type != "SRAM":
333        code_generation["zero_code"] += MEMSET_TEMPLATE.format(memory_type.lower())
334        code_generation["extern"] += EXTERN_LINKER_VAR_DECLARATION.format(
335            memory_type.lower(), "bss")
336
337    return code_generation
338
339
340def dump_header_file(header_file, code_generation):
341    code_string = ''
342    # create a dummy void function if there is no code to generate for
343    # bss/data/text regions
344
345    code_string += code_generation["extern"]
346
347    if code_generation["copy_code"]:
348        code_string += DATA_COPY_FUNCTION.format(code_generation["copy_code"])
349    else:
350        code_string += DATA_COPY_FUNCTION.format("void;")
351    if code_generation["zero_code"]:
352        code_string += BSS_ZEROING_FUNCTION.format(code_generation["zero_code"])
353    else:
354        code_string += BSS_ZEROING_FUNCTION.format("return;")
355
356    with open(header_file, "w") as header_file_desc:
357        header_file_desc.write(SOURCE_CODE_INCLUDES)
358        header_file_desc.write(code_string)
359
360
361def parse_args():
362    global args
363    parser = argparse.ArgumentParser(
364        description=__doc__,
365        formatter_class=argparse.RawDescriptionHelpFormatter)
366    parser.add_argument("-d", "--directory", required=True,
367                        help="obj file's directory")
368    parser.add_argument("-i", "--input_rel_dict", required=True,
369                        help="input src:memory type(sram2 or ccm or aon etc) string")
370    parser.add_argument("-o", "--output", required=False, help="Output ld file")
371    parser.add_argument("-s", "--output_sram_data", required=False,
372                        help="Output sram data ld file")
373    parser.add_argument("-b", "--output_sram_bss", required=False,
374                        help="Output sram bss ld file")
375    parser.add_argument("-c", "--output_code", required=False,
376                        help="Output relocation code header file")
377    parser.add_argument("-v", "--verbose", action="count", default=0,
378                        help="Verbose Output")
379    args = parser.parse_args()
380
381
382# return the absolute path for the object file.
383def get_obj_filename(searchpath, filename):
384    # get the object file name which is almost always pended with .obj
385    obj_filename = filename.split("/")[-1] + ".obj"
386
387    for dirpath, _, files in os.walk(searchpath):
388        for filename1 in files:
389            if filename1 == obj_filename:
390                if filename.split("/")[-2] in dirpath.split("/")[-1]:
391                    fullname = os.path.join(dirpath, filename1)
392                    return fullname
393
394
395# Create a dict with key as memory type and files as a list of values.
396def create_dict_wrt_mem():
397    # need to support wild card *
398    rel_dict = dict()
399    if args.input_rel_dict == '':
400        sys.exit("Disable CONFIG_CODE_DATA_RELOCATION if no file needs relocation")
401    for line in args.input_rel_dict.split(';'):
402        mem_region, file_name = line.split(':', 1)
403
404        file_name_list = glob.glob(file_name)
405        if not file_name_list:
406            warnings.warn("File: "+file_name+" Not found")
407            continue
408        if mem_region == '':
409            continue
410        if args.verbose:
411            print("Memory region ", mem_region, " Selected for file:", file_name_list)
412        if mem_region in rel_dict:
413            rel_dict[mem_region].extend(file_name_list)
414        else:
415            rel_dict[mem_region] = file_name_list
416
417    return rel_dict
418
419
420def main():
421    global mpu_align
422    mpu_align = {}
423    parse_args()
424    searchpath = args.directory
425    linker_file = args.output
426    sram_data_linker_file = args.output_sram_data
427    sram_bss_linker_file = args.output_sram_bss
428    rel_dict = create_dict_wrt_mem()
429    complete_list_of_sections = {}
430
431    # Create/or trucate file contents if it already exists
432    # raw = open(linker_file, "w")
433
434    # for each memory_type, create text/rodata/data/bss sections for all obj files
435    for memory_type, files in rel_dict.items():
436        full_list_of_sections = {"text": [], "rodata": [], "data": [], "bss": []}
437
438        for filename in files:
439            obj_filename = get_obj_filename(searchpath, filename)
440            # the obj file wasn't found. Probably not compiled.
441            if not obj_filename:
442                continue
443
444            full_list_of_sections = find_sections(obj_filename, full_list_of_sections)
445
446        # cleanup and attach the sections to the memory type after cleanup.
447        complete_list_of_sections = assign_to_correct_mem_region(memory_type,
448                                                                 full_list_of_sections,
449                                                                 complete_list_of_sections)
450
451    generate_linker_script(linker_file, sram_data_linker_file,
452                           sram_bss_linker_file, complete_list_of_sections)
453
454    code_generation = {"copy_code": '', "zero_code": '', "extern": ''}
455    for mem_type, list_of_sections in sorted(complete_list_of_sections.items()):
456        code_generation = generate_memcpy_code(mem_type,
457                                               list_of_sections, code_generation)
458
459    dump_header_file(args.output_code, code_generation)
460
461
462if __name__ == '__main__':
463    main()
464