Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
__init__.py
00001 """ 00002 mbed SDK 00003 Copyright (c) 2011-2016 ARM Limited 00004 00005 Licensed under the Apache License, Version 2.0 (the "License"); 00006 you may not use this file except in compliance with the License. 00007 You may obtain a copy of the License at 00008 00009 http://www.apache.org/licenses/LICENSE-2.0 00010 00011 Unless required by applicable law or agreed to in writing, software 00012 distributed under the License is distributed on an "AS IS" BASIS, 00013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00014 See the License for the specific language governing permissions and 00015 limitations under the License. 00016 """ 00017 from __future__ import print_function 00018 00019 import os 00020 import binascii 00021 import struct 00022 import shutil 00023 import inspect 00024 import sys 00025 from copy import copy 00026 from inspect import getmro 00027 from collections import namedtuple, Mapping 00028 from tools.targets.LPC import patch 00029 from tools.paths import TOOLS_BOOTLOADERS 00030 from tools.utils import json_file_to_dict 00031 00032 __all__ = ["target", "TARGETS", "TARGET_MAP", "TARGET_NAMES", "CORE_LABELS", 00033 "HookError", "generate_py_target", "Target", 00034 "CUMULATIVE_ATTRIBUTES", "get_resolution_order"] 00035 00036 CORE_LABELS = { 00037 "Cortex-M0" : ["M0", "CORTEX_M", "LIKE_CORTEX_M0", "CORTEX"], 00038 "Cortex-M0+": ["M0P", "CORTEX_M", "LIKE_CORTEX_M0", "CORTEX"], 00039 "Cortex-M1" : ["M1", "CORTEX_M", "LIKE_CORTEX_M1", "CORTEX"], 00040 "Cortex-M3" : ["M3", "CORTEX_M", "LIKE_CORTEX_M3", "CORTEX"], 00041 "Cortex-M4" : ["M4", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M4", "CORTEX"], 00042 "Cortex-M4F" : ["M4", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M4", "CORTEX"], 00043 "Cortex-M7" : ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"], 00044 "Cortex-M7F" : ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"], 00045 "Cortex-M7FD" : ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"], 00046 "Cortex-A9" : ["A9", "CORTEX_A", "LIKE_CORTEX_A9", "CORTEX"], 00047 "Cortex-M23": ["M23", "CORTEX_M", "LIKE_CORTEX_M23", "CORTEX"], 00048 "Cortex-M23-NS": ["M23", "CORTEX_M", "LIKE_CORTEX_M23", "CORTEX"], 00049 "Cortex-M33": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"], 00050 "Cortex-M33-NS": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"] 00051 } 00052 00053 ################################################################################ 00054 # Generic Target class that reads and interprets the data in targets.json 00055 00056 class HookError(Exception): 00057 """ A simple class that represents all the exceptions associated with 00058 hooking 00059 """ 00060 pass 00061 00062 CACHES = {} 00063 def cached (func): 00064 """A simple decorator used for automatically caching data returned by a 00065 function 00066 """ 00067 def wrapper(*args, **kwargs): 00068 """The wrapped function itself""" 00069 if (func.__name__, args) not in CACHES: 00070 CACHES[(func.__name__, args)] = func(*args, **kwargs) 00071 return CACHES[(func.__name__, args)] 00072 return wrapper 00073 00074 00075 # Cumulative attributes can have values appended to them, so they 00076 # need to be computed differently than regular attributes 00077 CUMULATIVE_ATTRIBUTES = ['extra_labels', 'macros', 'device_has', 'features'] 00078 00079 00080 def get_resolution_order (json_data, target_name, order, level=0): 00081 """ Return the order in which target descriptions are searched for 00082 attributes. This mimics the Python 2.2 method resolution order, which 00083 is what the old targets.py module used. For more details, check 00084 http://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path 00085 The resolution order contains (name, level) tuples, where "name" is the 00086 name of the class and "level" is the level in the inheritance hierarchy 00087 (the target itself is at level 0, its first parent at level 1, its 00088 parent's parent at level 2 and so on) 00089 """ 00090 # the resolution order can't contain duplicate target names 00091 if target_name not in [l[0] for l in order]: 00092 order.append((target_name, level)) 00093 parents = json_data[target_name].get("inherits", []) 00094 for par in parents: 00095 order = get_resolution_order(json_data, par, order, level + 1) 00096 return order 00097 00098 00099 def target (name, json_data): 00100 """Construct a target object""" 00101 resolution_order = get_resolution_order(json_data, name, []) 00102 resolution_order_names = [tgt for tgt, _ in resolution_order] 00103 return Target(name=name, 00104 json_data={key: value for key, value in json_data.items() 00105 if key in resolution_order_names}, 00106 resolution_order=resolution_order, 00107 resolution_order_names=resolution_order_names) 00108 00109 def generate_py_target (new_targets, name): 00110 """Add one or more new target(s) represented as a Python dictionary 00111 in 'new_targets'. It is an error to add a target with a name that 00112 already exists. 00113 """ 00114 base_targets = Target.get_json_target_data() 00115 for new_target in new_targets.keys(): 00116 if new_target in base_targets: 00117 raise Exception("Attempt to add target '%s' that already exists" 00118 % new_target) 00119 total_data = {} 00120 total_data.update(new_targets) 00121 total_data.update(base_targets) 00122 return target(name, total_data) 00123 00124 class Target (namedtuple("Target", "name json_data resolution_order resolution_order_names")): 00125 """An object to represent a Target (MCU/Board)""" 00126 00127 # Default location of the 'targets.json' file 00128 __targets_json_location_default = os.path.join( 00129 os.path.dirname(os.path.abspath(__file__)), '..', '..', 'targets', 'targets.json') 00130 00131 # Current/new location of the 'targets.json' file 00132 __targets_json_location = None 00133 00134 # Extra custom targets files 00135 __extra_target_json_files = [] 00136 00137 @staticmethod 00138 @cached 00139 def get_json_target_data (): 00140 """Load the description of JSON target data""" 00141 targets = json_file_to_dict(Target.__targets_json_location or 00142 Target.__targets_json_location_default) 00143 00144 for extra_target in Target.__extra_target_json_files: 00145 for k, v in json_file_to_dict(extra_target).items(): 00146 if k in targets: 00147 print('WARNING: Custom target "%s" cannot replace existing ' 00148 'target.' % k) 00149 else: 00150 targets[k] = v 00151 00152 return targets 00153 00154 @staticmethod 00155 def add_extra_targets(source_dir): 00156 extra_targets_file = os.path.join(source_dir, "custom_targets.json") 00157 if os.path.exists(extra_targets_file): 00158 Target.__extra_target_json_files.append(extra_targets_file) 00159 CACHES.clear() 00160 00161 @staticmethod 00162 def set_targets_json_location (location=None): 00163 """Set the location of the targets.json file""" 00164 Target.__targets_json_location = (location or 00165 Target.__targets_json_location_default) 00166 Target.__extra_target_json_files = [] 00167 # Invalidate caches, since the location of the JSON file changed 00168 CACHES.clear() 00169 00170 @staticmethod 00171 @cached 00172 def get_module_data (): 00173 """Get the members of this module using Python's "inspect" module""" 00174 return dict([(m[0], m[1]) for m in 00175 inspect.getmembers(sys.modules[__name__])]) 00176 00177 @staticmethod 00178 def __add_paths_to_progen(data): 00179 """Modify the exporter specification ("progen") by changing all 00180 "template" keys to full paths 00181 """ 00182 out = {} 00183 for key, val in data.items(): 00184 if isinstance(val, dict): 00185 out[key] = Target.__add_paths_to_progen(val) 00186 elif key == "template": 00187 out[key] = [os.path.join(os.path.dirname(__file__), 'export', v) 00188 for v in val] 00189 else: 00190 out[key] = val 00191 return out 00192 00193 def __getattr_cumulative(self, attrname): 00194 """Look for the attribute in the class and its parents, as defined by 00195 the resolution order 00196 """ 00197 tdata = self.json_data 00198 # For a cumulative attribute, figure out when it was defined the 00199 # last time (in attribute resolution order) then follow the "_add" 00200 # and "_remove" data fields 00201 for idx, tgt in enumerate(self.resolution_order): 00202 # the attribute was defined at this level in the resolution 00203 # order 00204 if attrname in tdata[tgt[0]]: 00205 def_idx = idx 00206 break 00207 else: 00208 raise AttributeError("Attribute '%s' not found in target '%s'" 00209 % (attrname, self.name)) 00210 # Get the starting value of the attribute 00211 starting_value = (tdata[self.resolution_order[def_idx][0]][attrname] 00212 or [])[:] 00213 # Traverse the resolution list in high inheritance to low 00214 # inheritance level, left to right order to figure out all the 00215 # other classes that change the definition by adding or removing 00216 # elements 00217 for idx in range(self.resolution_order[def_idx][1] - 1, -1, -1): 00218 same_level_targets = [tar[0] for tar in self.resolution_order 00219 if tar[1] == idx] 00220 for tar in same_level_targets: 00221 data = tdata[tar] 00222 # Do we have anything to add ? 00223 if (attrname + "_add") in data: 00224 starting_value.extend(data[attrname + "_add"]) 00225 # Do we have anything to remove ? 00226 if (attrname + "_remove") in data: 00227 # Macros can be defined either without a value (MACRO) 00228 # or with a value (MACRO=10). When removing, we specify 00229 # only the name of the macro, without the value. So we 00230 # need to create a mapping between the macro name and 00231 # its value. This will work for extra_labels and other 00232 # type of arrays as well, since they fall into the 00233 # "macros without a value" category (simple definitions 00234 # without a value). 00235 name_def_map = {} 00236 for crtv in starting_value: 00237 if crtv.find('=') != -1: 00238 temp = crtv.split('=') 00239 if len(temp) != 2: 00240 raise ValueError( 00241 "Invalid macro definition '%s'" % crtv) 00242 name_def_map[temp[0]] = crtv 00243 else: 00244 name_def_map[crtv] = crtv 00245 for element in data[attrname + "_remove"]: 00246 if element not in name_def_map: 00247 raise ValueError( 00248 ("Unable to remove '%s' in '%s.%s' since " 00249 % (element, self.name, attrname)) + 00250 "it doesn't exist") 00251 starting_value.remove(name_def_map[element]) 00252 return starting_value 00253 00254 def __getattr_helper(self, attrname): 00255 """Compute the value of a given target attribute""" 00256 if attrname in CUMULATIVE_ATTRIBUTES: 00257 return self.__getattr_cumulative (attrname) 00258 else: 00259 tdata = self.json_data 00260 starting_value = None 00261 for tgt in self.resolution_order: 00262 data = tdata[tgt[0]] 00263 try: 00264 return data[attrname] 00265 except KeyError: 00266 pass 00267 else: # Attribute not found 00268 raise AttributeError( 00269 "Attribute '%s' not found in target '%s'" 00270 % (attrname, self.name)) 00271 00272 def __getattr__ (self, attrname): 00273 """ Return the value of an attribute. This function only computes the 00274 attribute's value once, then adds it to the instance attributes (in 00275 __dict__), so the next time it is returned directly 00276 """ 00277 result = self.__getattr_helper (attrname) 00278 self.__dict__[attrname] = result 00279 return result 00280 00281 @staticmethod 00282 @cached 00283 def get_target (target_name): 00284 """ Return the target instance starting from the target name """ 00285 return target(target_name, Target.get_json_target_data()) 00286 00287 00288 @property 00289 def program_cycle_s (self): 00290 """Special override for program_cycle_s as it's default value depends 00291 upon is_disk_virtual 00292 """ 00293 try: 00294 return self.__getattr__ ("program_cycle_s") 00295 except AttributeError: 00296 return 4 if self.is_disk_virtual else 1.5 00297 00298 @property 00299 def labels (self): 00300 """Get all possible labels for this target""" 00301 names = copy(self.resolution_order_names) 00302 if "Target" in names: 00303 names.remove("Target") 00304 labels = (names + CORE_LABELS[self.core] + self.extra_labels) 00305 # Automatically define UVISOR_UNSUPPORTED if the target doesn't 00306 # specifically define UVISOR_SUPPORTED 00307 if "UVISOR_SUPPORTED" not in labels: 00308 labels.append("UVISOR_UNSUPPORTED") 00309 return labels 00310 00311 def init_hooks (self, hook, toolchain): 00312 """Initialize the post-build hooks for a toolchain. For now, this 00313 function only allows "post binary" hooks (hooks that are executed 00314 after the binary image is extracted from the executable file) 00315 00316 Positional Arguments: 00317 hook - the hook object to add post-binary-hooks to 00318 toolchain - the toolchain object for inspection 00319 """ 00320 00321 # If there's no hook, simply return 00322 try: 00323 hook_data = self.post_binary_hook 00324 except AttributeError: 00325 return 00326 # A hook was found. The hook's name is in the format 00327 # "classname.functionname" 00328 temp = hook_data["function"].split(".") 00329 if len(temp) != 2: 00330 raise HookError( 00331 ("Invalid format for hook '%s' in target '%s'" 00332 % (hook_data["function"], self.name)) + 00333 " (must be 'class_name.function_name')") 00334 class_name, function_name = temp 00335 # "class_name" must refer to a class in this file, so check if the 00336 # class exists 00337 mdata = self.get_module_data () 00338 if class_name not in mdata or \ 00339 not inspect.isclass(mdata[class_name]): 00340 raise HookError( 00341 ("Class '%s' required by '%s' in target '%s'" 00342 % (class_name, hook_data["function"], self.name)) + 00343 " not found in targets.py") 00344 # "function_name" must refer to a static function inside class 00345 # "class_name" 00346 cls = mdata[class_name] 00347 if (not hasattr(cls, function_name)) or \ 00348 (not inspect.isfunction(getattr(cls, function_name))): 00349 raise HookError( 00350 ("Static function '%s' " % function_name) + 00351 ("required by '%s' " % hook_data["function"]) + 00352 ("in target '%s' " % self.name) + 00353 ("not found in class '%s'" % class_name)) 00354 # Check if the hook specification also has toolchain restrictions 00355 toolchain_restrictions = set(hook_data.get("toolchains", [])) 00356 toolchain_labels = set(c.__name__ for c in getmro(toolchain.__class__)) 00357 if toolchain_restrictions and \ 00358 not toolchain_labels.intersection(toolchain_restrictions): 00359 return 00360 # Finally, hook the requested function 00361 hook.hook_add_binary("post", getattr(cls, function_name)) 00362 00363 ################################################################################ 00364 # Target specific code goes in this section 00365 # This code can be invoked from the target description using the 00366 # "post_binary_hook" key 00367 00368 class LPCTargetCode (object): 00369 """General LPC Target patching code""" 00370 @staticmethod 00371 def lpc_patch (t_self, resources, elf, binf): 00372 """Patch an elf file""" 00373 t_self.notify.debug("LPC Patch: %s" % os.path.split(binf)[1]) 00374 patch(binf) 00375 00376 class LPC4088Code (object): 00377 """Code specific to the LPC4088""" 00378 @staticmethod 00379 def binary_hook (t_self, resources, elf, binf): 00380 """Hook to be run after an elf file is built""" 00381 if not os.path.isdir(binf): 00382 # Regular binary file, nothing to do 00383 LPCTargetCode.lpc_patch(t_self, resources, elf, binf) 00384 return 00385 outbin = open(binf + ".temp", "wb") 00386 partf = open(os.path.join(binf, "ER_IROM1"), "rb") 00387 # Pad the fist part (internal flash) with 0xFF to 512k 00388 data = partf.read() 00389 outbin.write(data) 00390 outbin.write('\xFF' * (512*1024 - len(data))) 00391 partf.close() 00392 # Read and append the second part (external flash) in chunks of fixed 00393 # size 00394 chunksize = 128 * 1024 00395 partf = open(os.path.join(binf, "ER_IROM2"), "rb") 00396 while True: 00397 data = partf.read(chunksize) 00398 outbin.write(data) 00399 if len(data) < chunksize: 00400 break 00401 partf.close() 00402 outbin.close() 00403 # Remove the directory with the binary parts and rename the temporary 00404 # file to 'binf' 00405 shutil.rmtree(binf, True) 00406 os.rename(binf + '.temp', binf) 00407 t_self.notify.debug("Generated custom binary file (internal flash + SPIFI)") 00408 LPCTargetCode.lpc_patch(t_self, resources, elf, binf) 00409 00410 class TEENSY3_1Code (object): 00411 """Hooks for the TEENSY3.1""" 00412 @staticmethod 00413 def binary_hook (t_self, resources, elf, binf): 00414 """Hook that is run after elf is generated""" 00415 # This function is referenced by old versions of targets.json and should 00416 # be kept for backwards compatibility. 00417 pass 00418 00419 class MTSCode(object): 00420 """Generic MTS code""" 00421 @staticmethod 00422 def _combine_bins_helper(target_name, binf): 00423 """combine bins with the bootloader for a particular target""" 00424 loader = os.path.join(TOOLS_BOOTLOADERS, target_name, "bootloader.bin") 00425 target = binf + ".tmp" 00426 if not os.path.exists(loader): 00427 print("Can't find bootloader binary: " + loader) 00428 return 00429 outbin = open(target, 'w+b') 00430 part = open(loader, 'rb') 00431 data = part.read() 00432 outbin.write(data) 00433 outbin.write('\xFF' * (64*1024 - len(data))) 00434 part.close() 00435 part = open(binf, 'rb') 00436 data = part.read() 00437 outbin.write(data) 00438 part.close() 00439 outbin.seek(0, 0) 00440 data = outbin.read() 00441 outbin.seek(0, 1) 00442 crc = struct.pack('<I', binascii.crc32(data) & 0xFFFFFFFF) 00443 outbin.write(crc) 00444 outbin.close() 00445 os.remove(binf) 00446 os.rename(target, binf) 00447 00448 @staticmethod 00449 def combine_bins_mts_dot (t_self, resources, elf, binf): 00450 """A hook for the MTS MDOT""" 00451 MTSCode._combine_bins_helper("MTS_MDOT_F411RE", binf) 00452 00453 @staticmethod 00454 def combine_bins_mts_dragonfly (t_self, resources, elf, binf): 00455 """A hoof for the MTS Dragonfly""" 00456 MTSCode._combine_bins_helper("MTS_DRAGONFLY_F411RE", binf) 00457 00458 @staticmethod 00459 def combine_bins_mtb_mts_dragonfly (t_self, resources, elf, binf): 00460 """A hook for the MTB MTS Dragonfly""" 00461 MTSCode._combine_bins_helper("MTB_MTS_DRAGONFLY", binf) 00462 00463 class MCU_NRF51Code (object): 00464 """NRF51 Hooks""" 00465 @staticmethod 00466 def binary_hook (t_self, resources, _, binf): 00467 """Hook that merges the soft device with the bin file""" 00468 # Scan to find the actual paths of soft device 00469 sdf = None 00470 for softdevice_and_offset_entry\ 00471 in t_self.target.EXPECTED_SOFTDEVICES_WITH_OFFSETS: 00472 for hexf in resources.hex_files: 00473 if hexf.find(softdevice_and_offset_entry['name']) != -1: 00474 t_self.notify.debug("SoftDevice file found %s." 00475 % softdevice_and_offset_entry['name']) 00476 sdf = hexf 00477 00478 if sdf is not None: 00479 break 00480 if sdf is not None: 00481 break 00482 00483 if sdf is None: 00484 t_self.notify.debug("Hex file not found. Aborting.") 00485 return 00486 00487 # Look for bootloader file that matches this soft device or bootloader 00488 # override image 00489 blf = None 00490 if t_self.target.MERGE_BOOTLOADER is True: 00491 for hexf in resources.hex_files: 00492 if hexf.find(t_self.target.OVERRIDE_BOOTLOADER_FILENAME) != -1: 00493 t_self.notify.debug("Bootloader file found %s." 00494 % t_self.target.OVERRIDE_BOOTLOADER_FILENAME) 00495 blf = hexf 00496 break 00497 elif hexf.find(softdevice_and_offset_entry['boot']) != -1: 00498 t_self.notify.debug("Bootloader file found %s." 00499 % softdevice_and_offset_entry['boot']) 00500 blf = hexf 00501 break 00502 00503 # Merge user code with softdevice 00504 from intelhex import IntelHex 00505 binh = IntelHex() 00506 _, ext = os.path.splitext(binf) 00507 if ext == ".hex": 00508 binh.loadhex(binf) 00509 elif ext == ".bin": 00510 binh.loadbin(binf, softdevice_and_offset_entry['offset']) 00511 00512 if t_self.target.MERGE_SOFT_DEVICE is True: 00513 t_self.notify.debug("Merge SoftDevice file %s" 00514 % softdevice_and_offset_entry['name']) 00515 sdh = IntelHex(sdf) 00516 sdh.start_addr = None 00517 binh.merge(sdh) 00518 00519 if t_self.target.MERGE_BOOTLOADER is True and blf is not None: 00520 t_self.notify.debug("Merge BootLoader file %s" % blf) 00521 blh = IntelHex(blf) 00522 blh.start_addr = None 00523 binh.merge(blh) 00524 00525 with open(binf.replace(".bin", ".hex"), "w") as fileout: 00526 binh.write_hex_file(fileout, write_start_addr=False) 00527 00528 class NCS36510TargetCode: 00529 @staticmethod 00530 def ncs36510_addfib(t_self, resources, elf, binf): 00531 from tools.targets.NCS import add_fib_at_start 00532 print("binf ", binf) 00533 add_fib_at_start(binf[:-4]) 00534 00535 class RTL8195ACode : 00536 """RTL8195A Hooks""" 00537 @staticmethod 00538 def binary_hook(t_self, resources, elf, binf): 00539 from tools.targets.REALTEK_RTL8195AM import rtl8195a_elf2bin 00540 rtl8195a_elf2bin(t_self, elf, binf) 00541 ################################################################################ 00542 00543 # Instantiate all public targets 00544 def update_target_data(): 00545 TARGETS[:] = [Target.get_target(tgt) for tgt, obj 00546 in Target.get_json_target_data().items() 00547 if obj.get("public", True)] 00548 # Map each target name to its unique instance 00549 TARGET_MAP.clear() 00550 TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS])) 00551 TARGET_NAMES[:] = TARGET_MAP.keys() 00552 00553 TARGETS = [] 00554 TARGET_MAP = dict() 00555 TARGET_NAMES = [] 00556 00557 update_target_data() 00558 00559 # Some targets with different name have the same exporters 00560 EXPORT_MAP = {} 00561 00562 # Detection APIs 00563 def get_target_detect_codes (): 00564 """ Returns dictionary mapping detect_code -> platform_name 00565 """ 00566 result = {} 00567 for tgt in TARGETS: 00568 for detect_code in tgt.detect_code: 00569 result[detect_code] = tgt.name 00570 return result 00571 00572 def set_targets_json_location (location=None): 00573 """Sets the location of the JSON file that contains the targets""" 00574 # First instruct Target about the new location 00575 Target.set_targets_json_location(location) 00576 # Then re-initialize TARGETS, TARGET_MAP and TARGET_NAMES. The 00577 # re-initialization does not create new variables, it keeps the old ones 00578 # instead. This ensures compatibility with code that does 00579 # "from tools.targets import TARGET_NAMES" 00580 update_target_data() 00581
Generated on Tue Jul 12 2022 12:43:27 by
