Daiki Kato / mbed-os-lychee

Dependents:   mbed-os-example-blinky-gr-lychee GR-Boads_Camera_sample GR-Boards_Audio_Recoder GR-Boads_Camera_DisplayApp ... more

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