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