Anders Blomdell / mbed-sdk-tools
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers __init__.py Source File

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