Rtos API example

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