Skip to content

add Trace32 debugger support #1267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,46 @@ Arguments:
Used by:
- `OpenOCDDriver`_

USBLauterbachDebugger
~~~~~~~~~~~~~~~~~~~~~
An USBLauterbachDebugger resource describes a Lauterbach
PowerDebug/PowerTrace/uTrace debugger device connected via USB.

.. code-block:: yaml

USBLauterbachDebugger:
match:
ID_PATH: pci-0000:00:10.0-usb-0:1.4

.. code-block:: yaml

USBLauterbachDebugger:
match:
ID_SERIAL_SHORT: C230901234567

Arguments:
- match (dict): key and value pairs for a udev match, see `udev Matching`_

Used by:
- `LauterbachDriver`_

NetworkLauterbachDebugger
~~~~~~~~~~~~~~~~~~~~~~~~~
An NetworkLauterbachDebugger resource describes a Lauterbach
PowerDebug debugger device connected via Ethernet.

.. code-block:: yaml

NetworkLauterbachDebugger:
node: E230901234567

Arguments:
- node : Lauterbach NODENAME e.g. IP/NODENAME (factory default: serial number)
- protocol : optional, allows to select TCP instead of UDP, required for LG_PROXY support

Used by:
- `LauterbachDriver`_

SNMPEthernetPort
~~~~~~~~~~~~~~~~
A SNMPEthernetPort resource describes a port on an Ethernet switch, which is
Expand Down Expand Up @@ -1835,6 +1875,38 @@ Arguments:
- board_config (str): optional, board config in the ``openocd/scripts/board/`` directory
- load_commands (list of str): optional, load commands to use instead of ``init``, ``bootstrap {filename}``, ``shutdown``

LauterbachDriver
~~~~~~~~~~~~~~~~
A LauterbachDriver controls the “Lauterbach TRACE32 PowerView” to debug a target.

The ``t32_sys`` argument refers to paths declared in the environment configuration.

Binds to:
interface:
- `USBLauterbachDebugger`_
- `NetworkLauterbachDebugger`_

Implements:
- :any:`BootstrapProtocol`

.. code-block:: yaml

LauterbachDriver:
t32_bin: t32marm
t32_sys: t32_dvd202309

.. code-block:: yaml

paths:
t32_dvd202309: /opt/t32/t32/t32dvd202309

Arguments:
- t32_bin (str): optional, name of the TRACE32 architecture executable ``t32m*`` (default ``t32marm``)
- t32_sys (str): optional, base folder of the TRACE32 installation (default ENV['T32SYS'] or ``~/t32`` or ``/opt/t32``)
- script (str): optional, PRACTICE script (``.cmm``) executed on TRACE32 startup
- script_args_debug (list of str): optional, arguments passed to ``script`` to establish a debug session (Command ``debugger``)
- script_args_bootstrap (list of str): optional, arguments passed to ``script`` to bootstrap (Command ``bootstrap``)

QuartusHPSDriver
~~~~~~~~~~~~~~~~
A QuartusHPSDriver controls the "Quartus Prime Programmer and Tools" to flash
Expand Down
1 change: 1 addition & 0 deletions labgrid/driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@
from .deditecrelaisdriver import DeditecRelaisDriver
from .dediprogflashdriver import DediprogFlashDriver
from .httpdigitaloutput import HttpDigitalOutputDriver
from .lauterbachdriver import LauterbachDriver
235 changes: 235 additions & 0 deletions labgrid/driver/lauterbachdriver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import logging
import os
import re
import subprocess

from importlib import import_module

import attr

from .exception import ExecutionError
from ..factory import target_factory
from ..protocol import BootstrapProtocol
from ..resource.lauterbach import NetworkLauterbachDebugger, RemoteUSBLauterbachDebugger
from ..resource.udev import USBLauterbachDebugger
from ..step import step
from ..util.agentwrapper import AgentWrapper
from ..util.helper import get_uname_machine, processwrapper
from ..util.managedfile import ManagedFile
from ..util.proxy import proxymanager

from .common import Driver


@target_factory.reg_driver
@attr.s(eq=False)
class LauterbachDriver(Driver, BootstrapProtocol):
"""
Args:
t32_sys (str): base folder of the TRACE32 installation (default ENV['T32SYS'] or ~/t32 or /opt/t32)
t32_bin (str): name of the TRACE32 architecture executable `t32m*` (default `t32marm`)

script (str): path to the `.cmm` script to run on startup of TRACE32
first parameter is the used labgrid command e.g. `debugger, write-image`
script_args_debug (str, list):
parameters passed to `.cmm` script with labgrid command `debugger`
script_args_bootstrap (str, list):
parameters passed to `.cmm` script with labgrid command `bootstrap`
"""
bindings = {
"interface": {
"USBLauterbachDebugger",
"RemoteUSBLauterbachDebugger",
"NetworkLauterbachDebugger"
},
}
t32_bin = attr.ib(default="t32marm", validator=attr.validators.instance_of(str))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe have a look at the QEMUDriver's qemu_bin attribute.

t32_sys = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(str))
)
script = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(str))
)
script_args_debug = attr.ib(
default=attr.Factory(list),
validator=attr.validators.optional(attr.validators.instance_of((str,list)))
)
script_args_bootstrap = attr.ib(
default=attr.Factory(list),
validator=attr.validators.optional(attr.validators.instance_of((str,list)))
)

def __attrs_post_init__(self):
super().__attrs_post_init__()
self.logger = logging.getLogger(f"{self}:{self.target}")
self._pystart = import_module('lauterbach.trace32.pystart')

self._pathmap = {
"aarch64": "bin/linux-aarch64",
"amd64": "bin/pc_linux64",
"armhf": "bin/linux-armhf"
}

# detect TRACE32 installation folder via environment/T32SYS environment or default pathes
t32sys = []
if self.t32_sys:
# `t32_sys` may be absolute or a key in the path configuration
if os.path.isabs(self.t32_sys):
t32sys.append(self.t32_sys)
else:
t32sys.append(self.target.env.config.get_path(self.t32_sys))
else:
t32sys += [
os.environ.get("T32SYS", None),
"~/t32",
"/opt/t32"
]
# FIXME same as for openocd driver
# make sure we always have an environment or config
t32sys = filter(lambda x: x, t32sys)
if self.target.env:
t32sys = map(self.target.env.config.resolve_path, t32sys)
t32sys = list(filter(os.path.exists, t32sys))
if len(t32sys)==0:
raise ExecutionError("TRACE32 installation folder not found")
self.logger.info("Detected TRACE32 installation pathes [%s]", ", ".join(t32sys))
self.t32sys=t32sys[0]
self.logger.info("Using T32SYS: %s", self.t32sys)

# `script` may be absolute or a key in the path configuration
if self.script:
if not os.path.isabs(self.script):
self.script = self.target.env.config.get_path(self.script)
if self.target.env:
self.script = self.target.env.config.resolve_path(self.script)

self.connection = None
self.t32tcpusb = None

def _get_t32tcpusb_version(self):
# get version running `t32tcpusb` on client
t32tcpusb = os.path.join(self.t32sys, self._pathmap.get(get_uname_machine()), "t32tcpusb")
self.logger.debug("Using host t32tcpusb: %s", t32tcpusb)
if not os.path.exists(t32tcpusb):
raise ExecutionError(f"Tool 't32tcpusb' for host architecture '{get_uname_machine()}' path '{self._pathmap.get(get_uname_machine())}' missing")

version = 0x0
# convert 'Sw.Version: N.<year>.<month>.<revision>' to 0x<year><month>
output = processwrapper.check_output([t32tcpusb, '-h']).decode('utf-8')
versionmatch = re.search(r"Version:\s[NSRP]\.(\d{4})\.(\d{2})\.\d+", str(output))

if versionmatch is not None:
version = int(f'0x{versionmatch[1]}{versionmatch[2]}', 16)

return version

def on_activate(self):
if isinstance(self.interface, USBLauterbachDebugger):
self.connection = self._pystart.USBConnection(f"{self.interface.busnum:03d}:{self.interface.devnum:03d}")
elif isinstance(self.interface, NetworkLauterbachDebugger):
if self.interface.protocol.lower() == "udp":
host, port = proxymanager.get_host_and_port(self.interface, default_port=9187)
if host != self.interface.host:
raise ExecutionError("Proxy support not available for legacy Lauterbach devices")
self.connection = self._pystart.UDPConnection(self.interface.host)
else:
host, port = proxymanager.get_host_and_port(self.interface, default_port=9187)
self.connection = self._pystart.TCPConnection(host, port)
elif isinstance(self.interface, RemoteUSBLauterbachDebugger):
version = self._get_t32tcpusb_version()
if version<0x202212:
raise ExecutionError(f"Version {version:06x} of `t32tcpusb` too old")

agent = AgentWrapper(self.interface.host)
hosttools = agent.load('hosttools')
remoteArchitecture = hosttools.get_uname_machine()
self.logger.debug("Detected remote architecture: %s", remoteArchitecture)

t32tcpusb = os.path.join(self.t32sys, self._pathmap.get(remoteArchitecture), "t32tcpusb")
self.logger.debug("Using remote t32tcpusb: %s", t32tcpusb)
if not os.path.exists(t32tcpusb):
raise ExecutionError(f"Tool 't32tcpusb' for host architecture '{remoteArchitecture}' path '{self._pathmap.get(remoteArchitecture)}' missing")

mf = ManagedFile(t32tcpusb, self.interface)
mf.sync_to_resource()

remotePort = agent.get_free_port()
cmd = self.interface.command_prefix
cmd.insert(-2, "-tt") # force a tty in order to get `terminate` working
cmd += [
mf.get_remote_path(),
"--device",
f"{self.interface.busnum:03d}:{self.interface.devnum:03d}",
str(remotePort)
]

self.logger.debug("Running command '%s'", " ".join(cmd))
self.t32tcpusb = subprocess.Popen(cmd, stdout=subprocess.PIPE)

host, port = proxymanager.get_host_and_port(self.interface, default_port=remotePort)
self.connection = self._pystart.USBProxyConnection(host, port)
return super().on_activate()

def on_deactivate(self):
if self.t32tcpusb:
self.logger.debug("Try to terminate `t32tcpusb` on exporter")
self.t32tcpusb.terminate()

self.connection = None

return super().on_deactivate()

def _get_powerview_handle(self, command, script_args):
pv = self._pystart.PowerView(self.connection, system_path=self.t32sys)

if os.path.isabs(self.t32_bin):
pv.force_executable = self.t32_bin
else:
pv.target = self.t32_bin

if self.script:
pv.startup_script = self.script
pv.startup_parameter = [f"LABGRID_COMMAND={command.upper()}"]
if isinstance(script_args, list):
pv.startup_parameter += script_args
elif len(script_args) > 0:
pv.startup_parameter += [script_args]

return pv

# interactive debug
@Driver.check_active
def start(self):
pv = self._get_powerview_handle("DEBUGGER", self.script_args_debug)

self.logger.debug(pv.get_configuration_string())
pv.start()
pv.wait()

return

# Bootstrap protocol
@Driver.check_active
@step(args=['filename'])
def load(self, filename=None):
if self.script is None:
raise ExecutionError("Mandatory TRACE32 configuration `script` is missing")

if filename is None and self.image is not None:
filename = self.target.env.config.get_image_path(self.image)
mf = ManagedFile(filename, self.interface)
mf.sync_to_resource()

script_args = [f"FILE={filename}"]
script_args += self.script_args_bootstrap

pv = self._get_powerview_handle("BOOTSTRAP", script_args)
pv.screen = False

self.logger.debug(pv.get_configuration_string())
pv.start()
pv.wait()

return
12 changes: 12 additions & 0 deletions labgrid/remote/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,13 @@ def flashscript(self):
drv = self._get_driver_or_new(target, "FlashScriptDriver", name=name)
drv.flash(script=self.args.script, args=self.args.script_args)

def debugger(self):
place = self.get_acquired_place()
target = self._get_target(place)
name = self.args.name
drv = self._get_driver_or_new(target, "LauterbachDriver", name=name)
drv.start()

def bootstrap(self):
place = self.get_acquired_place()
target = self._get_target(place)
Expand Down Expand Up @@ -1646,6 +1653,11 @@ def main():
subparser.add_argument('--name', '-n', help="optional resource name")
subparser.set_defaults(func=ClientSession.flashscript)

subparser = subparsers.add_parser('debugger',
help="start a debugger")
subparser.add_argument('--name', '-n', help="optional resource name")
subparser.set_defaults(func=ClientSession.debugger)

subparser = subparsers.add_parser('bootstrap',
help="start a bootloader")
subparser.add_argument('-w', '--wait', type=float, default=10.0)
Expand Down
1 change: 1 addition & 0 deletions labgrid/remote/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ def __attrs_post_init__(self):
exports["USBSDMuxDevice"] = USBSDMuxExport
exports["USBSDWireDevice"] = USBSDWireExport
exports["USBDebugger"] = USBGenericExport
exports["USBLauterbachDebugger"] = USBGenericRemoteExport

exports["USBMassStorage"] = USBGenericExport
exports["USBVideo"] = USBGenericExport
Expand Down
2 changes: 2 additions & 0 deletions labgrid/resource/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
SigrokUSBSerialDevice,
USBAudioInput,
USBDebugger,
USBLauterbachDebugger,
USBFlashableDevice,
USBMassStorage,
USBNetworkInterface,
Expand All @@ -46,3 +47,4 @@
from .httpdigitalout import HttpDigitalOutput
from .sigrok import SigrokDevice
from .fastboot import AndroidNetFastboot
from .lauterbach import NetworkLauterbachDebugger
Loading