Skip to content

Commit

Permalink
exporter/driver: change t32tcpusb strategy for USBLauterbachDebugger
Browse files Browse the repository at this point in the history
Instead of invoking `t32tcpusb` from the client via SSH use a strategy that is
more like `ser2net`. Upload the `t32tcpusb` agent to the exporter, the exporter
notices this upload and restarts the matching `t32tcpusb` agent. The port used
by `t32tcpusb` is blocked by the exporter as long as no `t32tcpusb` agent is
uploaded.
  • Loading branch information
Alexander Merkle committed Sep 21, 2023
1 parent f21d593 commit ecda73d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 63 deletions.
69 changes: 6 additions & 63 deletions labgrid/driver/lauterbachdriver.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging
import os
import re
import subprocess
import time

from importlib import import_module

Expand All @@ -11,10 +10,8 @@
from ..protocol import DebuggerProtocol
from ..resource.lauterbach import NetworkLauterbachDebugger, NetworkUSBLauterbachDebugger
from ..resource.udev import USBLauterbachDebugger
from ..util.helper import get_uname_machine, processwrapper
from ..util.managedfile import ManagedFile
from ..util.proxy import proxymanager
from ..util import Timeout

from .common import Driver

Expand Down Expand Up @@ -54,81 +51,27 @@ def __attrs_post_init__(self):
self.t32sys=self.target.env.config.get_path(self.t32_sys)

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")
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):
self.connection = self._pystart.UDPConnection(self.interface.node)
elif isinstance(self.interface, NetworkUSBLauterbachDebugger):
port = "auto"
version = self._get_t32tcpusb_version()
if version<0x202212:
print(f"Version {version:06x} of `t32tcpusb` too old.")
return
if version<0x202309:
port = "8455"

path = os.path.join(self.t32sys, self.pathmap.get(self.interface.architecture), "t32tcpusb")
self.logger.debug("Upload %s to exporter", path)
mf = ManagedFile(path, self.interface)
mf.sync_to_resource()

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}",
port
]

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

# check output of `t32tcpusb` to get the used port
line = ""
timeout = Timeout(5.0)
while not timeout.expired:
if self.t32tcpusb.poll():
break
c = self.t32tcpusb.stdout.read(1).decode("utf-8")
if c=='\n':
self.logger.debug(line)
portmatch = re.search(r"TRACE32 TCP USB Proxy Service.*listening on port (\d+)\s", line)
if portmatch:
portused = int(portmatch[1])
break
line = ""
else:
line = line + c

if self.t32tcpusb.poll():
Exception("Agent `t32tcpusb` ended unexpectedly")
symlink = f"/var/cache/labgrid/t32tcpusb-{self.interface.path}"
mf.sync_to_resource(symlink=symlink)
# the exporter notices the change and restarts, for the moment give it some time
time.sleep(0.5)

host, port = proxymanager.get_host_and_port(self.interface, default_port=portused)
host, port = proxymanager.get_host_and_port(self.interface)
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()
Expand Down
115 changes: 115 additions & 0 deletions labgrid/remote/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sys
import os
import os.path
import re
import time
import traceback
import shutil
Expand Down Expand Up @@ -500,11 +501,125 @@ class USBLauterbachDebuggerExport(USBGenericExport):

def __attrs_post_init__(self):
super().__attrs_post_init__()
self.stat = None
self.port = None
self.socket = None
self.child = None
self.machine = get_uname_machine()

import random
for port in random.sample(range(8455,8555),100):
s = self._block_port(port)
if s is not None:
self.port = port
self.socket = s
break

def _block_port(self, port):
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
s = None
try:
s = socket(AF_INET, SOCK_STREAM)
except OSError:
s = None
if s is not None:
try:
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('', port))
s.listen()
except:
s.close()
s = None
self.logger.debug("block port %d", port)
return s

def _get_t32tcpusb_version(self, path):
"""Get version of `t32tcpusb`"""
version = 0x0
# convert 'Sw.Version: N.<year>.<month>.<revision>' to 0x<year><month>
output = subprocess.check_output([path, '-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 _get_start_params(self):
assert not self.broken
self.stat = None
symlink = f"/var/cache/labgrid/t32tcpusb-{self.local.path}"
try:
if os.path.exists(symlink):
stat = os.stat(symlink)
self.stat = (stat.st_ino, stat.st_size, stat.st_mtime)
except Exception as e:
self.logger.debug(e)
self.broken = f"cannot access {symlink}"
self.stat = None
if self.stat is None:
return None
return {
'stat': self.stat,
'symlink': symlink,
'busnum': self.local.busnum,
'devnum': self.local.devnum
}

def _start(self, start_params):
"""Start ``t32tcpusb`` subprocess"""
assert self.local.avail
assert self.child is None

# t32tcpusb available yet?
if start_params is None:
#self.logger.info("skip start of t32tcpusb (Reason: link %s-<path> not found)", self.t32tcpusb_basename)
return

tcpusbVersion = self._get_t32tcpusb_version(start_params['symlink'])

cmd = [start_params['symlink'],]
if tcpusbVersion >= 0x202212:
cmd += [
'--device',
f'{start_params["busnum"]:03}:{start_params["devnum"]:03}'
]
cmd += [f'{self.port}']
self.logger.info("starting t32tcpusb with: %s", " ".join(cmd))
self.socket.close()
self.child = subprocess.Popen(cmd)
try:
self.child.wait(timeout=0.5)
raise ExporterError("t32tcpusb exited immediately")
except subprocess.TimeoutExpired:
# good, t32tcpusb didn't exit immediately
pass
self.logger.info("started t32tcpusb for bus %03d device %03d on port %d", start_params['busnum'], start_params['devnum'], self.port)

def _stop(self, start_params):
"""Stop ``t32tcpusb`` subprocess"""
if start_params is None:
return

assert self.child
child = self.child
self.child = None
child.terminate()
try:
child.wait(2.0) # t32tcpusb takes about a second to react
except subprocess.TimeoutExpired:
self.logger.warning("t32tcpusb still running after SIGTERM")
log_subprocess_kernel_stack(self.logger, child)
child.kill()
child.wait(1.0)
self.logger.info("stopped t32tcpusb for bus %03d device %03d on port %d", start_params['busnum'], start_params['devnum'], self.port)
self._block_port(self.port)

def _get_params(self):
"""Helper function to return parameters"""
p = super()._get_params()
p['architecture'] = get_uname_machine()
p['port'] = self.port
return p

@attr.s(eq=False)
Expand Down
2 changes: 2 additions & 0 deletions labgrid/resource/lauterbach.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ class NetworkUSBLauterbachDebugger(RemoteUSBResource):
Args:
architecture (str): Architecture of the exporter userspace
port (str): socket port to connect to
"""
architecture = attr.ib(validator=attr.validators.instance_of(str))
port = attr.ib(validator=attr.validators.instance_of(int))
def __attrs_post_init__(self):
self.timeout = 10.0
super().__attrs_post_init__()

0 comments on commit ecda73d

Please sign in to comment.