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