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