# Copyright (c) 2020 Synopsys. # # SPDX-License-Identifier: Apache-2.0 '''Runners for Synopsys Metaware Debugger(mdb).''' import shutil import time import os from os import path from runners.core import ZephyrBinaryRunner, RunnerCaps try: import psutil MISSING_REQUIREMENTS = False except ImportError: MISSING_REQUIREMENTS = True # normally we should create class with common functionality inherited from # ZephyrBinaryRunner and inherit MdbNsimBinaryRunner and MdbHwBinaryRunner # from it. However as we do lookup for runners with # ZephyrBinaryRunner.__subclasses__() such sub-sub-classes won't be found. # So, we move all common functionality to helper functions instead. def simulation_run(mdb_runner): return mdb_runner.nsim_args != '' def get_cld_pid(mdb_process): try: parent = psutil.Process(mdb_process.pid) children = parent.children(recursive=True) for process in children: if process.name().startswith("cld"): return (True, process.pid) except psutil.Error: pass return (False, -1) # MDB creates child process (cld) which won't be terminated if we simply # terminate parents process (mdb). 'record_cld_pid' is provided to record 'cld' # process pid to file (mdb.pid) so this process can be terminated correctly by # twister infrastructure def record_cld_pid(mdb_runner, mdb_process): for _i in range(100): found, pid = get_cld_pid(mdb_process) if found: mdb_pid_file = path.join(mdb_runner.build_dir, 'mdb.pid') mdb_runner.logger.debug("MDB CLD pid: " + str(pid) + " " + mdb_pid_file) with open(mdb_pid_file, 'w') as f: f.write(str(pid)) return time.sleep(0.05) def mdb_do_run(mdb_runner, command): commander = "mdb" mdb_runner.require(commander) mdb_basic_options = ['-nooptions', '-nogoifmain', '-toggle=include_local_symbols=1'] # remove previous .sc.project folder which has temporary settings # for MDB. This is useful for troubleshooting situations with # unexpected behavior of the debugger mdb_cfg_dir = path.join(mdb_runner.build_dir, '.sc.project') if path.exists(mdb_cfg_dir): shutil.rmtree(mdb_cfg_dir) # nsim if simulation_run(mdb_runner): mdb_target = ['-nsim', '@' + mdb_runner.nsim_args] # hardware target else: if mdb_runner.jtag == 'digilent': mdb_target = ['-digilent', mdb_runner.dig_device] else: # \todo: add support of other debuggers mdb_target = [''] if command == 'flash': if simulation_run(mdb_runner): # for nsim , can't run and quit immediately mdb_run = ['-run', '-cl'] else: mdb_run = ['-run', '-cmd=-nowaitq run', '-cmd=quit', '-cl'] elif command == 'debug': # use mdb gui to debug mdb_run = ['-OKN'] if mdb_runner.cores == 1: # single core's mdb command is different with multicores mdb_cmd = ([commander] + mdb_basic_options + mdb_target + mdb_run + [mdb_runner.elf_name]) elif 1 < mdb_runner.cores <= 4: mdb_multifiles = '-multifiles=' for i in range(mdb_runner.cores): # note that: mdb requires -pset starting from 1, not 0 !!! mdb_sub_cmd = ([commander] + ['-pset={}'.format(i + 1), '-psetname=core{}'.format(i), # -prop=download=2 is used for SMP application debug, only the 1st # core will download the shared image. ('-prop=download=2' if i > 0 else '')] + mdb_basic_options + mdb_target + [mdb_runner.elf_name]) mdb_runner.check_call(mdb_sub_cmd) mdb_multifiles += ('core{}'.format(mdb_runner.cores-1-i) if i == 0 else ',core{}'.format(mdb_runner.cores-1-i)) # to enable multi-core aware mode for use with the MetaWare debugger, # need to set the NSIM_MULTICORE environment variable to a non-zero value if simulation_run(mdb_runner): os.environ["NSIM_MULTICORE"] = '1' mdb_cmd = ([commander] + [mdb_multifiles] + mdb_run) else: raise ValueError('unsupported cores {}'.format(mdb_runner.cores)) process = mdb_runner.popen_ignore_int(mdb_cmd) record_cld_pid(mdb_runner, process) class MdbNsimBinaryRunner(ZephyrBinaryRunner): '''Runner front-end for nSIM via mdb.''' def __init__(self, cfg, cores=1, nsim_args=''): super().__init__(cfg) self.jtag = '' self.cores = int(cores) if nsim_args != '': self.nsim_args = path.join(cfg.board_dir, 'support', nsim_args) else: self.nsim_args = '' self.elf_name = cfg.elf_file self.build_dir = cfg.build_dir self.dig_device = '' @classmethod def name(cls): return 'mdb-nsim' @classmethod def capabilities(cls): return RunnerCaps(commands={'flash', 'debug'}) @classmethod def do_add_parser(cls, parser): parser.add_argument('--cores', default=1, help='''choose the cores that target has, e.g. --cores=1''') parser.add_argument('--nsim_args', default='', help='''if given, arguments for nsim simulator through mdb which should be in /support, e.g. --nsim-args= mdb_em.args''') @classmethod def do_create(cls, cfg, args): return MdbNsimBinaryRunner( cfg, cores=args.cores, nsim_args=args.nsim_args) def do_run(self, command, **kwargs): mdb_do_run(self, command) class MdbHwBinaryRunner(ZephyrBinaryRunner): '''Runner front-end for mdb.''' def __init__(self, cfg, cores=1, jtag='digilent', dig_device=''): super().__init__(cfg) self.jtag = jtag self.cores = int(cores) self.nsim_args = '' self.elf_name = cfg.elf_file if dig_device != '': self.dig_device = '-prop=dig_device=' + dig_device else: self.dig_device = '' self.build_dir = cfg.build_dir @classmethod def name(cls): return 'mdb-hw' @classmethod def capabilities(cls): return RunnerCaps(commands={'flash', 'debug'}) @classmethod def do_add_parser(cls, parser): parser.add_argument('--jtag', default='digilent', help='''choose the jtag interface for hardware targets, e.g. --jtat=digilent for digilent jtag adapter''') parser.add_argument('--cores', default=1, help='''choose the number of cores that target has, e.g. --cores=1''') parser.add_argument('--dig-device', default='', help='''choose the the specific digilent device to connect, this is useful when multiple targets are connected''') @classmethod def do_create(cls, cfg, args): return MdbHwBinaryRunner( cfg, cores=args.cores, jtag=args.jtag, dig_device=args.dig_device) def do_run(self, command, **kwargs): if MISSING_REQUIREMENTS: raise RuntimeError('one or more Python dependencies were missing; ' "see the getting started guide for details on " "how to fix") mdb_do_run(self, command)