Knight KE / Mbed OS Game_Master
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            "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