• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Converts ST generated .icf linker files into basic .ld linker files"""
15
16from typing import Dict, Optional, Tuple
17
18import pathlib
19
20
21def parse_icf(icf_file: str) -> Tuple[Dict, Dict]:
22    """Parse ICF linker file.
23
24    ST only provides .icf linker files for many products, so there is a need
25    to generate basic GCC compatible .ld files for all products.
26    This parses the basic features from the .icf format well enough to work
27    for the ST's .icf files that exist in `cmsis_device`
28
29    Args:
30        icf_file: .icf linker file read into a string
31
32    Returns:
33        (regions, blocks) where
34            `regions` is a map from region_name -> (start_hex, end_hex)
35            `blocks` is a map from block_name -> {feature_1: val_1,...}
36
37    Raises:
38        IndexError if .icf is malformed (at least compared to how ST makes them)
39    """
40    symbols = {}
41    regions = {}  # region: (start_addr, end_addr)
42    blocks = {}
43    for line in icf_file.split('\n'):
44        line = line.strip()
45        if line == '' or line.startswith('/*') or line.startswith('//'):
46            continue
47        tokens = line.split()
48        if len(tokens) < 2:
49            continue
50        if tokens[0] == 'define':
51            if tokens[1] == 'symbol':
52                symbols[tokens[2]] = tokens[4].strip(';')
53            elif tokens[1] == 'region':
54                regions[tokens[2].split('_')[0]] = (tokens[5],
55                                                    tokens[7].strip('];'))
56            elif tokens[1] == 'block':
57                blocks[tokens[2]] = {
58                    tokens[4]: tokens[6].strip(','),
59                    tokens[7]: tokens[9]
60                }
61    parsed_regions = {
62        region: (symbols[start] if start in symbols else start,
63                 symbols[end] if end in symbols else end)
64        for region, (start, end) in regions.items()
65    }
66
67    parsed_blocks = {
68        name:
69        {k: symbols[v] if v in symbols else v
70         for k, v in fields.items()}
71        for name, fields in blocks.items()
72    }
73
74    return (parsed_regions, parsed_blocks)
75
76
77def icf_regions_to_ld_regions(icf_regions: Dict) -> Dict:
78    """Converts .icf regions to .ld regions
79
80    The .icf format specifies the beginning and end of each region, while
81    .ld expects the beginning and a length string.
82
83    Args:
84        icf_regions: icf_regions parsed with `parse_icf()`
85
86    Returns:
87        A map from `region_name` -> (start_hex, length_str)
88    """
89    ld_regions = {}
90    for region, (start, end) in icf_regions.items():
91        start_dec = int(start, 16)
92        end_dec = int(end, 16)
93        length = end_dec - start_dec + 1
94        length_str = str(length)
95        if length % 1024 == 0:
96            length_str = f'{int(length/1024)}K'
97
98        # Some icf scripts incorrectly have an exclusive region end.
99        # This corrects for that.
100        elif (length - 1) % 1024 == 0:
101            length_str = f'{int((length-1)/1024)}K'
102
103        # ST's gcc linker scripts generally use FLASH instead of ROM
104        if region == 'ROM':
105            region = 'FLASH'
106
107        ld_regions[region] = (start, length_str)
108
109    return ld_regions
110
111
112def create_ld(ld_regions: Dict, blocks: Dict) -> str:
113    """Create .ld file from template.
114
115    This creates a barebones .ld file that *should* work for most single core
116    stm32 families. It only contains regions for RAM and FLASH.
117
118    This template can be bypassed in GN if a more sophisticated linker file
119    is required.
120
121    Args:
122        ld_regions: generated by `icf_regions_to_ld_regions()`
123        blocks: generated by `parse_icf`
124
125    Returns:
126        a string linker file with the RAM/FLASH specified by the given reginos.
127
128    Raises:
129        KeyError if ld_regions does not contain 'RAM' and 'FLASH'
130    """
131    return f"""\
132ENTRY(Reset_Handler)
133_estack = ORIGIN(RAM) + LENGTH(RAM);
134
135_Min_Heap_Size = {blocks['HEAP']['size']};
136_Min_Stack_Size = {blocks['CSTACK']['size']};
137
138MEMORY
139{{
140  RAM (xrw) : ORIGIN = {ld_regions['RAM'][0]}, LENGTH = {ld_regions['RAM'][1]}
141  FLASH (rx) : ORIGIN = {ld_regions['FLASH'][0]}, LENGTH = {ld_regions['FLASH'][1]}
142}}
143
144SECTIONS
145{{
146  .isr_vector :
147  {{
148    . = ALIGN(8);
149    KEEP(*(.isr_vector))
150    . = ALIGN(8);
151  }} >FLASH
152
153  .text :
154  {{
155    . = ALIGN(8);
156    *(.text)
157    *(.text*)
158    *(.glue_7)
159    *(.glue_7t)
160    *(.eh_frame)
161
162    KEEP (*(.init))
163    KEEP (*(.fini))
164
165    . = ALIGN(8);
166    _etext = .;
167  }} >FLASH
168
169  .rodata :
170  {{
171    . = ALIGN(8);
172    *(.rodata)
173    *(.rodata*)
174    . = ALIGN(8);
175  }} >FLASH
176
177  .ARM.extab   : {{
178    . = ALIGN(8);
179    *(.ARM.extab* .gnu.linkonce.armextab.*)
180    . = ALIGN(8);
181  }} >FLASH
182
183  .ARM : {{
184    . = ALIGN(8);
185    __exidx_start = .;
186    *(.ARM.exidx*)
187    __exidx_end = .;
188    . = ALIGN(8);
189  }} >FLASH
190
191  .preinit_array     :
192  {{
193    . = ALIGN(8);
194    PROVIDE_HIDDEN (__preinit_array_start = .);
195    KEEP (*(.preinit_array*))
196    PROVIDE_HIDDEN (__preinit_array_end = .);
197    . = ALIGN(8);
198  }} >FLASH
199
200  .init_array :
201  {{
202    . = ALIGN(8);
203    PROVIDE_HIDDEN (__init_array_start = .);
204    KEEP (*(SORT(.init_array.*)))
205    KEEP (*(.init_array*))
206    PROVIDE_HIDDEN (__init_array_end = .);
207    . = ALIGN(8);
208  }} >FLASH
209
210  .fini_array :
211  {{
212    . = ALIGN(8);
213    PROVIDE_HIDDEN (__fini_array_start = .);
214    KEEP (*(SORT(.fini_array.*)))
215    KEEP (*(.fini_array*))
216    PROVIDE_HIDDEN (__fini_array_end = .);
217    . = ALIGN(8);
218  }} >FLASH
219
220  _sidata = LOADADDR(.data);
221  .data :
222  {{
223    . = ALIGN(8);
224    _sdata = .;
225    *(.data)
226    *(.data*)
227    . = ALIGN(8);
228    _edata = .;
229  }} >RAM AT> FLASH
230
231  . = ALIGN(8);
232  .bss :
233  {{
234    _sbss = .;
235    __bss_start__ = _sbss;
236    *(.bss)
237    *(.bss*)
238    *(COMMON)
239
240    . = ALIGN(8);
241    _ebss = .;
242    __bss_end__ = _ebss;
243  }} >RAM
244
245  ._user_heap_stack :
246  {{
247    . = ALIGN(8);
248    PROVIDE ( end = . );
249    PROVIDE ( _end = . );
250    . = . + _Min_Heap_Size;
251    . = . + _Min_Stack_Size;
252    . = ALIGN(8);
253  }} >RAM
254
255  /DISCARD/ :
256  {{
257    libc.a ( * )
258    libm.a ( * )
259    libgcc.a ( * )
260  }}
261
262  .ARM.attributes 0 : {{ *(.ARM.attributes) }}
263}}
264    """
265
266
267def icf_to_ld(icf_path: pathlib.Path, ld_path: Optional[pathlib.Path]):
268    """Convert icf file into an ld file.
269
270    Note: This only works for ST generated .icf files.
271
272    Args:
273        icf_path: path to .icf file to convert
274        ld_path: path to write generated .ld file or None.
275                 If None, the .ld file is written to stdout.
276    """
277    with open(icf_path, 'rb') as icf_file:
278        icf_str = icf_file.read().decode('utf-8', errors='ignore')
279
280    icf_regions, blocks = parse_icf(icf_str)
281    ld_regions = icf_regions_to_ld_regions(icf_regions)
282    ld_str = create_ld(ld_regions, blocks)
283
284    if ld_path:
285        with open(ld_path, 'w') as ld_file:
286            ld_file.write(ld_str)
287    else:
288        print(ld_str)
289