• 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]] = (
55                    tokens[5],
56                    tokens[7].strip('];'),
57                )
58            elif tokens[1] == 'block':
59                blocks[tokens[2]] = {
60                    tokens[4]: tokens[6].strip(','),
61                    tokens[7]: tokens[9],
62                }
63    parsed_regions = {
64        region: (
65            symbols[start] if start in symbols else start,
66            symbols[end] if end in symbols else end,
67        )
68        for region, (start, end) in regions.items()
69    }
70
71    parsed_blocks = {
72        name: {k: symbols[v] if v in symbols else v for k, v in fields.items()}
73        for name, fields in blocks.items()
74    }
75
76    return (parsed_regions, parsed_blocks)
77
78
79def icf_regions_to_ld_regions(icf_regions: Dict) -> Dict:
80    """Converts .icf regions to .ld regions
81
82    The .icf format specifies the beginning and end of each region, while
83    .ld expects the beginning and a length string.
84
85    Args:
86        icf_regions: icf_regions parsed with `parse_icf()`
87
88    Returns:
89        A map from `region_name` -> (start_hex, length_str)
90    """
91    ld_regions = {}
92    for region, (start, end) in icf_regions.items():
93        start_dec = int(start, 16)
94        end_dec = int(end, 16)
95        length = end_dec - start_dec + 1
96        length_str = str(length)
97        if length % 1024 == 0:
98            length_str = f'{int(length/1024)}K'
99
100        # Some icf scripts incorrectly have an exclusive region end.
101        # This corrects for that.
102        elif (length - 1) % 1024 == 0:
103            length_str = f'{int((length-1)/1024)}K'
104
105        # ST's gcc linker scripts generally use FLASH instead of ROM
106        if region == 'ROM':
107            region = 'FLASH'
108
109        ld_regions[region] = (start, length_str)
110
111    return ld_regions
112
113
114def create_ld(ld_regions: Dict, blocks: Dict) -> str:
115    """Create .ld file from template.
116
117    This creates a barebones .ld file that *should* work for most single core
118    stm32 families. It only contains regions for RAM and FLASH.
119
120    This template can be bypassed in GN if a more sophisticated linker file
121    is required.
122
123    Args:
124        ld_regions: generated by `icf_regions_to_ld_regions()`
125        blocks: generated by `parse_icf`
126
127    Returns:
128        a string linker file with the RAM/FLASH specified by the given reginos.
129
130    Raises:
131        KeyError if ld_regions does not contain 'RAM' and 'FLASH'
132    """
133    return f"""\
134ENTRY(Reset_Handler)
135_estack = ORIGIN(RAM) + LENGTH(RAM);
136
137_Min_Heap_Size = {blocks['HEAP']['size']};
138_Min_Stack_Size = {blocks['CSTACK']['size']};
139
140MEMORY
141{{
142  RAM (xrw) : ORIGIN = {ld_regions['RAM'][0]}, LENGTH = {ld_regions['RAM'][1]}
143  FLASH (rx) : ORIGIN = {ld_regions['FLASH'][0]}, LENGTH = {ld_regions['FLASH'][1]}
144}}
145
146SECTIONS
147{{
148
149  /* The ARMv8-M architecture requires this is at least aligned to 128 bytes,
150   * and aligned to a power of two that is greater than 4 times the number of
151   * supported exceptions. 512 has been selected as it accommodates most vector
152   * tables.
153   */
154  .isr_vector :
155  {{
156    . = ALIGN(512);
157    KEEP(*(.isr_vector))
158    . = ALIGN(4);
159  }} >FLASH
160
161  .text :
162  {{
163    . = ALIGN(4);
164    *(.text)
165    *(.text*)
166    *(.glue_7)
167    *(.glue_7t)
168    *(.eh_frame)
169
170    KEEP (*(.init))
171    KEEP (*(.fini))
172
173    . = ALIGN(4);
174    _etext = .;
175  }} >FLASH
176
177  .rodata :
178  {{
179    . = ALIGN(4);
180    *(.rodata)
181    *(.rodata*)
182    . = ALIGN(4);
183  }} >FLASH
184
185  .ARM.extab   : {{
186    . = ALIGN(4);
187    *(.ARM.extab* .gnu.linkonce.armextab.*)
188    . = ALIGN(4);
189  }} >FLASH
190
191  .ARM : {{
192    . = ALIGN(4);
193    __exidx_start = .;
194    *(.ARM.exidx*)
195    __exidx_end = .;
196    . = ALIGN(4);
197  }} >FLASH
198
199  .preinit_array     :
200  {{
201    . = ALIGN(4);
202    PROVIDE_HIDDEN (__preinit_array_start = .);
203    KEEP (*(.preinit_array*))
204    PROVIDE_HIDDEN (__preinit_array_end = .);
205    . = ALIGN(4);
206  }} >FLASH
207
208  .init_array :
209  {{
210    . = ALIGN(4);
211    PROVIDE_HIDDEN (__init_array_start = .);
212    KEEP (*(SORT(.init_array.*)))
213    KEEP (*(.init_array*))
214    PROVIDE_HIDDEN (__init_array_end = .);
215    . = ALIGN(4);
216  }} >FLASH
217
218  .fini_array :
219  {{
220    . = ALIGN(4);
221    PROVIDE_HIDDEN (__fini_array_start = .);
222    KEEP (*(SORT(.fini_array.*)))
223    KEEP (*(.fini_array*))
224    PROVIDE_HIDDEN (__fini_array_end = .);
225    . = ALIGN(4);
226  }} >FLASH
227
228  _sidata = LOADADDR(.data);
229  .data :
230  {{
231    . = ALIGN(4);
232    _sdata = .;
233    *(.data)
234    *(.data*)
235    . = ALIGN(4);
236    _edata = .;
237  }} >RAM AT> FLASH
238
239  . = ALIGN(4);
240  .bss :
241  {{
242    _sbss = .;
243    __bss_start__ = _sbss;
244    *(.bss)
245    *(.bss*)
246    *(COMMON)
247
248    . = ALIGN(4);
249    _ebss = .;
250    __bss_end__ = _ebss;
251  }} >RAM
252
253  /* The ARMv7-M architecture may require 8-byte alignment of the stack pointer
254   * rather than 4 in some contexts and implementations, so this region is
255   * 8-byte aligned (see ARMv7-M Architecture Reference Manual DDI0403E
256   * section B1.5.7).
257   */
258  ._user_heap_stack :
259  {{
260    . = ALIGN(8);
261    PROVIDE ( end = . );
262    PROVIDE ( _end = . );
263    . = . + _Min_Heap_Size;
264    . = . + _Min_Stack_Size;
265    . = ALIGN(8);
266  }} >RAM
267
268  /DISCARD/ :
269  {{
270    libc.a ( * )
271    libm.a ( * )
272    libgcc.a ( * )
273  }}
274
275  .ARM.attributes 0 : {{ *(.ARM.attributes) }}
276}}
277    """
278
279
280def icf_to_ld(icf_path: pathlib.Path, ld_path: Optional[pathlib.Path]):
281    """Convert icf file into an ld file.
282
283    Note: This only works for ST generated .icf files.
284
285    Args:
286        icf_path: path to .icf file to convert
287        ld_path: path to write generated .ld file or None.
288                 If None, the .ld file is written to stdout.
289    """
290    with open(icf_path, 'rb') as icf_file:
291        icf_str = icf_file.read().decode('utf-8', errors='ignore')
292
293    icf_regions, blocks = parse_icf(icf_str)
294    ld_regions = icf_regions_to_ld_regions(icf_regions)
295    ld_str = create_ld(ld_regions, blocks)
296
297    if ld_path:
298        with open(ld_path, 'w') as ld_file:
299            ld_file.write(ld_str)
300    else:
301        print(ld_str)
302