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