1# Copyright 2023 NXP
2# Copyright (c) 2017 Linaro Limited.
3#
4# SPDX-License-Identifier: Apache-2.0
5#
6# Based on jlink.py
7
8'''Runner for debugging with NXP's LinkServer.'''
9
10import logging
11import os
12import shlex
13import subprocess
14import sys
15
16from runners.core import ZephyrBinaryRunner, RunnerCaps
17
18
19DEFAULT_LINKSERVER_EXE = 'Linkserver.exe' if sys.platform == 'win32' else 'LinkServer'
20DEFAULT_LINKSERVER_GDB_PORT =  3333
21DEFAULT_LINKSERVER_SEMIHOST_PORT = 3334
22
23class LinkServerBinaryRunner(ZephyrBinaryRunner):
24    '''Runner front-end for NXP Linkserver'''
25    def __init__(self, cfg, device, core,
26                 linkserver=DEFAULT_LINKSERVER_EXE,
27                 dt_flash=True, erase=True,
28                 probe=1,
29                 gdb_host='',
30                 gdb_port=DEFAULT_LINKSERVER_GDB_PORT,
31                 semihost_port=DEFAULT_LINKSERVER_SEMIHOST_PORT,
32                 override=[],
33                 tui=False, tool_opt=[]):
34        super().__init__(cfg)
35        self.file = cfg.file
36        self.file_type = cfg.file_type
37        self.hex_name = cfg.hex_file
38        self.bin_name = cfg.bin_file
39        self.elf_name = cfg.elf_file
40        self.gdb_cmd = cfg.gdb if cfg.gdb else None
41        self.device = device
42        self.core = core
43        self.linkserver = linkserver
44        self.dt_flash = dt_flash
45        self.erase = erase
46        self.probe = probe
47        self.gdb_host = gdb_host
48        self.gdb_port = gdb_port
49        self.semihost_port = semihost_port
50        self.tui_arg = ['-tui'] if tui else []
51        self.override = override
52        self.override_cli = self._build_override_cli()
53
54        self.tool_opt = []
55        for opts in [shlex.split(opt) for opt in tool_opt]:
56            self.tool_opt += opts
57
58    @classmethod
59    def name(cls):
60        return 'linkserver'
61
62    @classmethod
63    def capabilities(cls):
64        return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
65                          dev_id=True, flash_addr=True, erase=True,
66                          tool_opt=True, file=True)
67
68    @classmethod
69    def do_add_parser(cls, parser):
70        parser.add_argument('--device', required=True, help='device name')
71
72        parser.add_argument('--core', required=False, help='core of the device')
73
74        parser.add_argument('--probe', default=1,
75                            help='interface to use (index, no serial number), default is 1')
76
77        parser.add_argument('--tui', default=False, action='store_true',
78                            help='if given, GDB uses -tui')
79
80        parser.add_argument('--gdb-port', default=DEFAULT_LINKSERVER_GDB_PORT,
81                            help='gdb port to open, defaults to {}'.format(
82                               DEFAULT_LINKSERVER_GDB_PORT))
83
84        parser.add_argument('--semihost-port', default=DEFAULT_LINKSERVER_SEMIHOST_PORT,
85                            help='semihost port to open, defaults to the empty string '
86                            'and runs a gdb server')
87        # keep this, we have to assume that the default 'commander' is on PATH
88        parser.add_argument('--linkserver', default=DEFAULT_LINKSERVER_EXE,
89                            help=f'''LinkServer executable, default is
90                            {DEFAULT_LINKSERVER_EXE}''')
91        # user may need to override settings.
92        parser.add_argument('--override', required=False, action='append',
93                            help=f'''configuration overrides as defined bylinkserver. Example: /device/memory/0/location=0xcafecafe''')
94
95    @classmethod
96    def do_create(cls, cfg, args):
97
98        return LinkServerBinaryRunner(cfg, args.device, args.core,
99                                 linkserver=args.linkserver,
100                                 dt_flash=args.dt_flash,
101                                 erase=args.erase,
102                                 probe=args.probe,
103                                 semihost_port=args.semihost_port,
104                                 gdb_port=args.gdb_port,
105                                 override=args.override,
106                                 tui=args.tui, tool_opt=args.tool_opt)
107
108    @property
109    def linkserver_version_str(self):
110
111        if not hasattr(self, '_linkserver_version'):
112            linkserver_version_cmd=[self.linkserver, "-v"]
113            ls_output=self.check_output(linkserver_version_cmd)
114            self.linkserver_version = str(ls_output.split()[1].decode()).lower()
115
116        return self.linkserver_version
117
118    def do_run(self, command, **kwargs):
119
120        self.linkserver = self.require(self.linkserver)
121        self.logger.info(f'LinkServer: {self.linkserver}, version {self.linkserver_version_str}')
122
123        if command == 'flash':
124            self.flash(**kwargs)
125        else:
126            if self.core is not None:
127                _cmd_core = [ "-c",  self.core ]
128            else:
129                _cmd_core = []
130
131            linkserver_cmd = ([self.linkserver] +
132                              ["gdbserver"]    +
133                              ["--probe", "#"+str(self.probe) ] +
134                              ["--gdb-port", str(self.gdb_port )] +
135                              ["--semihost-port", str(self.semihost_port) ] +
136                              _cmd_core +
137                              self.override_cli +
138                              [self.device])
139
140            self.logger.debug(f'LinkServer cmd:  + {linkserver_cmd}')
141
142            if command in ('debug', 'attach'):
143                if self.elf_name is  None or not os.path.isfile(self.elf_name):
144                    raise ValueError('Cannot debug; elf file required')
145
146                gdb_cmd = ([self.gdb_cmd] +
147                           self.tui_arg +
148                           [self.elf_name] +
149                           ['-ex', 'target remote {}:{}'.format(self.gdb_host, self.gdb_port)])
150
151                if command == 'debug':
152                    gdb_cmd += [ '-ex', 'load', '-ex', 'monitor reset']
153
154                if command == 'attach':
155                    linkserver_cmd += ['--attach']
156
157                self.run_server_and_client(linkserver_cmd, gdb_cmd)
158
159            elif command == 'debugserver':
160                if self.gdb_host:
161                    raise ValueError('Cannot run debugserver with --gdb-host')
162
163                self.check_call(linkserver_cmd)
164
165    def do_erase(self, **kwargs):
166
167        if self.core is not None:
168            _cmd_core = ":"+self.core
169        else:
170            _cmd_core = ""
171
172        linkserver_cmd = ([self.linkserver, "flash"] + ["--probe", "#"+str(self.probe)] +
173                          [self.device+_cmd_core] + ["erase"])
174        self.logger.debug("flash erase command = " + str(linkserver_cmd))
175        self.check_call(linkserver_cmd)
176
177    def _build_override_cli(self):
178
179        override_cli = []
180
181        if self.override is not None:
182            for ov in self.override:
183                override_cli = (override_cli + ["-o", str(ov)])
184
185        return override_cli
186
187    def flash(self, **kwargs):
188
189        if self.core is not None:
190            _cmd_core = ":"+self.core
191        else:
192            _cmd_core = ""
193
194        linkserver_cmd = ([self.linkserver, "flash"] + ["--probe", "#"+str(self.probe)] + self.override_cli + [self.device+_cmd_core])
195        self.logger.debug(f'LinkServer cmd:  + {linkserver_cmd}')
196
197        if self.erase:
198            self.do_erase()
199
200        if self.bin_name is not None and os.path.isfile(self.bin_name):
201            if self.dt_flash:
202                load_addr = self.flash_address_from_build_conf(self.build_conf)
203            else:
204                self.logger.critical("no load flash address could be found...")
205                raise RuntimeError("no load flash address could be found...")
206
207            flash_cmd = (["load", "--addr", str(load_addr), self.bin_name])
208        else:
209            err = 'Cannot flash; no bin ({}) file found.'
210            raise ValueError(err.format(self.bin_name))
211
212        # Flash the selected elf file
213        linkserver_cmd = linkserver_cmd + flash_cmd
214        self.logger.debug("flash command = " + str(linkserver_cmd))
215        kwargs = {}
216        if not self.logger.isEnabledFor(logging.DEBUG):
217            if self.linkserver_version_str < "v1.3.15":
218                kwargs['stderr'] = subprocess.DEVNULL
219            else:
220                kwargs['stdout'] = subprocess.DEVNULL
221
222        self.check_call(linkserver_cmd, **kwargs)
223