Newer
Older
mbed-os / tools / export / uvision / __init__.py
@Chun-Chieh Li Chun-Chieh Li on 25 May 2021 15 KB M2354: Support TF-M
from __future__ import print_function, absolute_import
from builtins import str

import os
from os.path import normpath, exists, dirname, join, abspath, relpath
import ntpath
import copy
from collections import namedtuple
import shutil
from subprocess import Popen, PIPE
import re
import json

from tools.resources import FileType
from tools.targets import TARGET_MAP, CORE_ARCH
from tools.export.exporters import Exporter
from tools.export.cmsis import DeviceCMSIS


class DeviceUvision(DeviceCMSIS):
    """Uvision Device class, inherits CMSIS Device class

    Encapsulates information necessary for uvision project targets"""
    def __init__(self, target):
        DeviceCMSIS.__init__(self, target)
        dev_format = "$$Device:{0}${1}"
        self.svd = ''
        if self.debug_svd:
            self.svd = dev_format.format(self.dname, self.debug_svd)
        self.debug_interface = self.uv_debug()
        self.flash_dll = self.generate_flash_dll()

    def uv_debug(self):
        """Return a namedtuple of information about uvision debug settings"""
        UVDebug = namedtuple('UVDebug', ['bin_loc', 'core_flag', 'key'])

        # CortexMXn => pCMX
        cpu = self.core.replace("Cortex-", "C")
        cpu = cpu.replace("+", "")
        cpu = cpu.replace("F", "")
        cpu = cpu.replace("-NS", "")
        cpu_flag = "p"+cpu

        # Locations found in Keil_v5/TOOLS.INI
        debuggers = {
            "st-link": ('STLink\\ST-LINKIII-KEIL_SWO.dll',
                        'ST-LINKIII-KEIL_SWO'),
            "j-link": ('Segger\\JL2CM3.dll', 'JL2CM3'),
            "cmsis-dap": ('BIN\\CMSIS_AGDI.dll', 'CMSIS_AGDI'),
            "nulink": ('NULink\\Nu_Link.dll', 'Nu_Link')
        }
        res = debuggers[self.debug.lower()]
        binary = res[0]
        key = res[1]

        return UVDebug(binary, cpu_flag, key)

    def generate_flash_dll(self):
        '''Flash DLL string from uvision
        S = SW/JTAG Clock ID
        C = CPU index in JTAG chain
        P = Access Port
        For the Options for Target -> Debug -> settings -> "Flash" dialog:
        FD = RAM Start for Flash Functions
        FC = RAM Size for Flash Functions
        FN = Number of Flash types
        FF = Flash File Name (without an extension)
        FS = Start Address of the Flash Device
        FL = Size of the Flash Device
        FP = Full path to the Device algorithm (RTE)

        Necessary to flash some targets.
        '''
        fl_count = 0

        RAMS = [
            (info["start"], info["size"])
            for mem, info in self.target_info["memories"].items()
            if "RAM" in mem
        ]
        format_str = (
            "UL2CM3(-S0 -C0 -P0 -FD{ramstart}"
            " -FC{ramsize} -FN{num_algos} {extra_flags})"
        )
        ramstart = ''
        # Default according to Keil developer
        ramsize = '1000'
        if len(RAMS) >= 1:
            ramstart = '{:x}'.format(RAMS[0][0])
        extra_flags = []
        for info in self.target_info["algorithms"]:
            if not info:
                continue
            if not info["default"]:
                continue
            name = info['file_name']
            name_reg = "\w*[/\\\\]([\w_]+)\.flm"
            m = re.search(name_reg, name.lower())
            fl_name = m.group(1) if m else None
            name_flag = "-FF" + str(fl_count) + fl_name

            start = '{:x}'.format(info["start"])
            size = '{:x}'.format(info["size"])
            rom_start_flag = "-FS" + str(fl_count) + str(start)
            rom_size_flag = "-FL" + str(fl_count) + str(size)

            if info["ram_start"] is not None and info["ram_size"] is not None:
                ramstart = '{:x}'.format(info["ram_start"])
                ramsize = '{:x}'.format(info["ram_size"])

            path_flag = "-FP{}($$Device:{}${})".format(
                str(fl_count), self.dname, name
            )

            extra_flags.extend([
                name_flag, rom_start_flag, rom_size_flag, path_flag
            ])
            fl_count += 1

        extra = " ".join(extra_flags)
        return format_str.format(ramstart=ramstart,
                                 ramsize=ramsize,
                                 extra_flags=extra, num_algos=fl_count)


class Uvision(Exporter):
    """Keil Uvision class

    This class encapsulates information to be contained in a Uvision
    project file (.uvprojx).
    The needed information can be viewed in uvision.tmpl
    """

    POST_BINARY_WHITELIST = set([
        "LPCTargetCode.lpc_patch",
        "MTSCode.combine_bins_mts_dragonfly",
        "LPC55S69Code.binary_hook",
        "M2351Code.merge_secure",
        "M2354Code.merge_secure"
    ])

    # File associations within .uvprojx file
    file_types = {'.cpp': 8, '.c': 1, '.s': 2,
                  '.obj': 3, '.o': 3, '.lib': 4,
                  '.ar': 4, '.h': 5, '.hpp': 5, '.sct': 4}

    def uv_files(self, files):
        """An generator containing Uvision specific information about project files
        Positional Arguments:
        files - the location of source files

        .uvprojx XML for project file:
        <File>
            <FileType>{{file.type}}</FileType>
            <FileName>{{file.name}}</FileName>
            <FilePath>{{file.loc}}</FilePath>
        </File>
        """
        for loc in files:
            # Encapsulates the information necessary for template entry above
            UVFile = namedtuple('UVFile', ['type', 'loc', 'name'])
            _, ext = os.path.splitext(loc)
            if ext.lower() in self.file_types:
                type = self.file_types[ext.lower()]
                name = ntpath.basename(normpath(loc))
                yield UVFile(type, loc, name)

    def format_flags(self):
        """Format toolchain flags for Uvision"""
        flags = copy.deepcopy(self.flags)
        asm_flag_string = (
            '--cpreproc --cpreproc_opts=-D__ASSERT_MSG,' +
            ",".join("-D{}".format(s) for s in
                     self.toolchain.get_symbols(for_asm=True)))
        flags['asm_flags'] = asm_flag_string

        config_header = self.config_header_ref
        config_option = self.toolchain.get_config_option(config_header.name)
        c_flags = set(
            flags['c_flags'] + flags['cxx_flags'] + flags['common_flags']
        )
        in_template = set(
            ["--no_vla", "--cpp", "--cpp11", "--c99", "-MMD"] + config_option
        )

        def valid_flag(x):
            return (
                x not in in_template and
                not x.startswith("-O") and
                not x.startswith("-std") and
                not x.startswith("-D")
            )

        def is_define(s):
            return s.startswith("-D") and "(" not in s

        flags['c_flags'] = " ".join(
            f.replace('"', '\\"') for f in c_flags if valid_flag(f)
        )
        flags['c_flags'] += " "
        flags['c_flags'] += " ".join(config_option)
        flags['c_defines'] = " ".join(f[2:].replace('"', '\\"')
                                      for f in c_flags if is_define(f))
        flags['ld_flags'] = " ".join(set(flags['ld_flags']))
        return flags

    def format_src(self, srcs):
        """Make sources into the named tuple for use in the template"""
        grouped = self.group_project_files(srcs)
        for group, files in grouped.items():
            grouped[group] = sorted(list(self.uv_files(files)),
                                    key=lambda tuple: tuple[2].lower())
        return grouped

    @staticmethod
    def format_fpu(core):
        """Generate a core's FPU string"""
        fpu_core_name = core.replace("-NS", "").rstrip("E")
        if fpu_core_name.endswith("FD"):
            return "FPU3(DFPU)"
        elif fpu_core_name.endswith("F"):
            if CORE_ARCH[core] == 8:
                return "FPU3(SFPU)"
            return "FPU2"
        else:
            return ""

    def generate(self):
        """Generate the .uvproj file"""
        srcs = (
            self.resources.headers + self.resources.s_sources +
            self.resources.c_sources + self.resources.cpp_sources +
            self.resources.objects + self.libraries
        )
        ctx = {
            'name': self.project_name,
            # project_files => dict of generators - file group to generator of
            # UVFile tuples defined above
            'project_files': sorted(list(self.format_src(srcs).items()),
                                    key=lambda tuple: tuple[0].lower()),
            'include_paths': ';'.join(self.filter_dot(d) for d in
                                      self.resources.inc_dirs),
            'device': DeviceUvision(self.target),
            'postbuild_step_active': 0,
        }

        if self.toolchain.config.has_regions and not self.zip:
            # Serialize region information
            export_info = {}
            restrict_size = getattr(self.toolchain.config.target, "restrict_size")
            if restrict_size:
                export_info["target"] = {
                    "restrict_size": restrict_size
                }

            binary_path = "BUILD/{}.hex".format(self.project_name)
            region_list = list(self.toolchain.config.regions)
            export_info["region_list"] = [
                r._replace(filename=binary_path) if r.active else r for r in region_list
            ]
            # Enable the post build step
            postbuild_script_path = join(relpath(dirname(__file__)), "postbuild.py")
            ctx['postbuild_step'] = (
                'python {} "$K\\" "#L"'.format(postbuild_script_path)
            )
            ctx['postbuild_step_active'] = 1
            ctx['export_info'] = json.dumps(export_info, indent=4)

        sct_file_ref = self.resources.get_file_refs(FileType.LD_SCRIPT)[0]
        sct_file_ref = self.toolchain.correct_scatter_shebang(
            sct_file_ref, dirname(sct_file_ref.name)
        )
        self.resources.add_file_ref(
            FileType.LD_SCRIPT, sct_file_ref.name, sct_file_ref.path
        )
        ctx['linker_script'] = sct_file_ref.name
        fpu_included_core_name = ctx['device'].core.replace("-NS", "")
        ctx['cputype'] = fpu_included_core_name.rstrip("FDE")
        if fpu_included_core_name.endswith("FD"):
            ctx['fpu_setting'] = 3
        elif fpu_included_core_name.rstrip("E").endswith("F"):
            ctx['fpu_setting'] = 2
        else:
            ctx['fpu_setting'] = 1
        ctx['fputype'] = self.format_fpu(ctx['device'].core)
        ctx['armc6'] = int(self.TOOLCHAIN is 'ARMC6')
        ctx['toolchain_name'] = self.TOOLCHAIN_NAME

        std = [flag for flag in self.flags['c_flags'] if flag.startswith("-std=")]
        if len(std) >= 1:
            std = std[-1][len('-std='):]
        else:
            std = None
        c_std = {
            'c89': 1, 'gnu89': 2,
            'c90': 1, 'gnu90': 2,
            'c99': 3, 'gnu99': 4,
            'c11': 5, 'gnu11': 6,
        }
        ctx['v6_lang'] = c_std.get(std, 0)

        std = [flag for flag in self.flags['cxx_flags'] if flag.startswith("-std=")]
        if len(std) >= 1:
            std = std[-1][len('-std='):]
        else:
            std = None
        cpp_std = {
            'c++98': 1, 'gnu++98': 2,
            'c++03': 5, 'gnu++03': 2, # UVision 5.28 does not support gnu++03 - fall back to gnu++98
            'c++11': 3, 'gnu++11': 4,
            'c++14': 6, 'gnu++14': 7, # UVision 5.28 or later is required for gnu++14, c++14 or gnu++17
            'c++17': 8, 'gnu++17': 9,
        }
        ctx['v6_lang_p'] = cpp_std.get(std, 0)

        ctx.update(self.format_flags())
        self.gen_file(
            'uvision/uvision.tmpl', ctx, self.project_name + ".uvprojx"
        )
        self.gen_file(
            'uvision/uvision_debug.tmpl', ctx, self.project_name + ".uvoptx"
        )

        if ctx['postbuild_step_active']:
            self.gen_file(
                'uvision/debug_init.ini', ctx, 'debug_init.ini'
            )
            self.gen_file(
                'uvision/flash_init.ini', ctx, 'flash_init.ini'
            )
            self.gen_file(
                'uvision/export_info.tmpl', ctx, 'export_info.json'
            )

    @staticmethod
    def clean(project_name):
        os.remove(project_name + ".uvprojx")
        os.remove(project_name + ".uvoptx")

        if exists("export_info.json"):
            os.remove("export_info.json")
        if exists("debug_init.ini"):
            os.remove("debug_init.ini")
        if exists("flash_init.ini"):
            os.remove("flash_init.ini")

        # legacy .build directory cleaned if exists
        if exists('.build'):
            shutil.rmtree('.build')
        if exists('BUILD'):
            shutil.rmtree('BUILD')

    @staticmethod
    def build(project_name, log_name='build_log.txt', cleanup=True):
        """ Build Uvision project """
        # > UV4 -r -j0 -o [log_name] [project_name].uvprojx
        proj_file = project_name + ".uvprojx"
        cmd = ['UV4', '-r', '-j0', '-o', log_name, proj_file]

        # Build the project
        p = Popen(cmd, stdout=PIPE, stderr=PIPE)
        out, err = p.communicate()
        ret_code = p.returncode

        # Print the log file to stdout
        with open(log_name, 'r') as f:
            print(f.read())

        # Cleanup the exported and built files
        if cleanup:
            os.remove(log_name)
            Uvision.clean(project_name)

        # Returns 0 upon success, 1 upon a warning, and neither upon an error
        if ret_code != 0 and ret_code != 1:
            # Seems like something went wrong.
            return -1
        else:
            return 0


class UvisionArmc5(Uvision):
    NAME = 'uvision5-armc5'
    TOOLCHAIN = 'ARM'
    TOOLCHAIN_NAME = ''

    @classmethod
    def is_target_supported(cls, target_name):
        target = TARGET_MAP[target_name]
        if not target.is_TFM_target:
            if int(target.build_tools_metadata["version"]) > 0:
                # Just check for ARMC5 as ARMC5 must be there irrespective of whether uARM is there or not if the target is staying with ARMC5
                if "ARMC5" not in target.supported_toolchains:
                    return False
            else:
                if not (set(target.supported_toolchains).intersection(
                        set(["ARM", "uARM"]))):
                    return False

            if not DeviceCMSIS.check_supported(target_name):
                return False
            if "Cortex-A" in target.core:
                return False
            if not hasattr(target, "post_binary_hook"):
                return True
            if target.post_binary_hook['function'] in cls.POST_BINARY_WHITELIST:
                return True
            else:
                return False
        else:
            return False


class UvisionArmc6(Uvision):
    NAME = 'uvision5-armc6'
    TOOLCHAIN = 'ARMC6'
    TOOLCHAIN_NAME = '6070000::V6.7::.\ARMCLANG'

    @classmethod
    def is_target_supported(cls, target_name):
        target = TARGET_MAP[target_name]
        if not target.is_TFM_target:
            if int(target.build_tools_metadata["version"]) > 0:
                if not len(set(target.supported_toolchains).intersection(
                        set(["ARM", "ARMC6"]))) > 0:
                    return False
            else:
                if "ARMC6" not in target.supported_toolchains:
                    return False

            if not DeviceCMSIS.check_supported(target_name):
                return False
            if "Cortex-A" in target.core:
                return False
            if not hasattr(target, "post_binary_hook"):
                return True
            if target.post_binary_hook['function'] in cls.POST_BINARY_WHITELIST:
                return True
            else:
                return False
        else:
            return False