Lee Kai Xuan / mbed-os

Fork of mbed-os by erkin yucel

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers targets.py Source File

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()