joey shelton / LED_Demo2

Dependencies:   MAX44000 PWM_Tone_Library nexpaq_mdk

Fork of LED_Demo by joey shelton

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