|
3 | 3 |
|
4 | 4 | import argparse |
5 | 5 | import asyncio |
| 6 | +import importlib |
6 | 7 | import json |
7 | 8 | import logging |
8 | 9 | import sys |
9 | 10 | import os |
10 | 11 | import os.path |
11 | 12 | import signal |
| 13 | +import tempfile |
12 | 14 | import time |
13 | 15 | import traceback |
14 | 16 | import shutil |
|
19 | 21 | from socket import gethostname, getfqdn |
20 | 22 | import attr |
21 | 23 | from autobahn.asyncio.wamp import ApplicationRunner, ApplicationSession |
22 | | -import importlib |
23 | 24 |
|
24 | 25 | from .config import ResourceConfig |
25 | 26 | from .common import ResourceEntry, enable_tcp_nodelay, monkey_patch_max_msg_payload_size_ws_option |
@@ -301,75 +302,140 @@ def _stop(self, start_params): |
301 | 302 |
|
302 | 303 |
|
303 | 304 | @attr.s(eq=False) |
304 | | -class QuartusServerExport(ResourceExport): |
| 305 | +class NetworkInterfaceExport(ResourceExport): |
| 306 | + """ResourceExport for a network interface""" |
| 307 | + |
| 308 | + def __attrs_post_init__(self): |
| 309 | + super().__attrs_post_init__() |
| 310 | + if self.cls == "NetworkInterface": |
| 311 | + from ..resource.base import NetworkInterface |
| 312 | + |
| 313 | + self.local = NetworkInterface(target=None, name=None, **self.local_params) |
| 314 | + elif self.cls == "USBNetworkInterface": |
| 315 | + from ..resource.udev import USBNetworkInterface |
| 316 | + |
| 317 | + self.local = USBNetworkInterface(target=None, name=None, **self.local_params) |
| 318 | + self.data["cls"] = "RemoteNetworkInterface" |
| 319 | + |
| 320 | + def _get_params(self): |
| 321 | + """Helper function to return parameters""" |
| 322 | + params = { |
| 323 | + "host": self.host, |
| 324 | + "ifname": self.local.ifname, |
| 325 | + } |
| 326 | + if self.cls == "USBNetworkInterface": |
| 327 | + params["extra"] = { |
| 328 | + "state": self.local.if_state, |
| 329 | + } |
| 330 | + |
| 331 | + return params |
| 332 | + |
| 333 | + |
| 334 | +exports["USBNetworkInterface"] = NetworkInterfaceExport |
| 335 | +exports["NetworkInterface"] = NetworkInterfaceExport |
| 336 | + |
| 337 | + |
| 338 | +@attr.s(eq=False) |
| 339 | +class USBGenericExport(ResourceExport): |
| 340 | + """ResourceExport for USB devices accessed directly from userspace""" |
| 341 | + |
| 342 | + def __attrs_post_init__(self): |
| 343 | + super().__attrs_post_init__() |
| 344 | + local_cls_name = self.cls |
| 345 | + self.data["cls"] = f"Network{self.cls}" |
| 346 | + from ..resource import udev |
| 347 | + |
| 348 | + local_cls = getattr(udev, local_cls_name) |
| 349 | + self.local = local_cls(target=None, name=None, **self.local_params) |
| 350 | + |
| 351 | + def _get_params(self): |
| 352 | + """Helper function to return parameters""" |
| 353 | + return { |
| 354 | + "host": self.host, |
| 355 | + "busnum": self.local.busnum, |
| 356 | + "devnum": self.local.devnum, |
| 357 | + "path": self.local.path, |
| 358 | + "vendor_id": self.local.vendor_id, |
| 359 | + "model_id": self.local.model_id, |
| 360 | + } |
| 361 | + |
| 362 | + |
| 363 | +@attr.s(eq=False) |
| 364 | +class QuartusServerExport(USBGenericExport): |
305 | 365 | """ ResourceExport for a QuartusUSBJTAG via ``jtagd``""" |
306 | 366 |
|
307 | 367 | def __attrs_post_init__(self): |
308 | 368 | super().__attrs_post_init__() |
309 | | - self.data['cls'] = "NetworkQuartusUSBJTAG" |
310 | | - from ..resource.udev import QuartusUSBJTAG |
311 | | - self.local = QuartusUSBJTAG(target=None, name=None, **self.local_params) |
312 | 369 | self.child = None |
| 370 | + self.jtagd_port = self.local.jtagd_port |
| 371 | + self.cfg_tempfile = None |
313 | 372 |
|
314 | 373 | def __del__(self): |
315 | 374 | if self.child is not None: |
316 | | - self.release() |
317 | | - |
318 | | - def _get_custom_config_file_name(self): |
319 | | - serialNumber = self.local.device_serial |
320 | | - return os.path.join(self.local.jtagd_file_locations, f"jtagd_cfg_{serialNumber}_{self.local.jtagd_port}.conf") |
| 375 | + self.stop() |
321 | 376 |
|
322 | | - def acquire(self, *args, **kwargs): |
| 377 | + def _start(self, start_params): |
323 | 378 | """Start ``jtagd`` subprocess""" |
324 | 379 | assert self.local.avail |
| 380 | + assert self.child is None |
325 | 381 |
|
326 | | - cfgFileName = self._get_custom_config_file_name() |
| 382 | + if not self.jtagd_port: |
| 383 | + self.jtagd_port = get_free_port() |
327 | 384 |
|
328 | | - with open(cfgFileName, 'w+') as file: |
329 | | - file.write(f"Password = \"{self.local.jtagd_password}\";") |
| 385 | + self.cfg_tempfile = tempfile.NamedTemporaryFile() |
| 386 | + self.cfg_tempfile.write( |
| 387 | + f"Password = \"{self.local.jtagd_password}\";".encode('utf-8')) |
| 388 | + self.cfg_tempfile.flush() |
330 | 389 |
|
331 | | - #find the right path to the library |
| 390 | + # find the right path to the library and modify the env |
332 | 391 | lib_path = importlib.machinery.PathFinder.find_spec('libhwsf').origin |
| 392 | + ld_preload = [lib_path, os.getenv('LD_PRELOAD', "")] |
| 393 | + os.environ["LD_PRELOAD"] = os.pathsep.join(ld_preload) |
| 394 | + os.environ["HWSF_DEV"] = "path:" + self.local.device.sys_name |
333 | 395 |
|
334 | | - #get the usb path from the device serial number |
335 | | - serialNumber = self.local.device_serial |
336 | | - dev_grep = "grep {SERIAL} /sys/bus/usb/devices/*/serial | cut -d '/' -f6".format(SERIAL=serialNumber) |
337 | | - dev_path, _ = subprocess.Popen(dev_grep, shell=True, stdout=subprocess.PIPE).communicate() |
338 | | - |
339 | | - my_env = os.environ.copy() |
340 | | - my_env["LD_PRELOAD"] = os.pathsep.join(filter(None, [lib_path, os.environ.get('LD_PRELOAD')])) |
341 | | - my_env["HWSF_DEV"] = "path:" + dev_path.decode("utf-8").replace("\n","") |
| 396 | + cmd = f"{self.local.jtagd_cmd} --foreground --port {self.jtagd_port} --config {self.cfg_tempfile.name}" |
342 | 397 |
|
343 | | - cmd = " ".join([f"{self.local.jtagd_cmd}", |
344 | | - f"--foreground", |
345 | | - f"--port {self.local.jtagd_port}", |
346 | | - f"--config {self._get_custom_config_file_name()}"]) |
| 398 | + self.logger.info("starting jtagd for %s on port %s with command LD_PRELOAD+=%s HWSF_DEV=%s %s", |
| 399 | + self.local.device.sys_name, self.jtagd_port, lib_path, |
| 400 | + os.environ['HWSF_DEV'], cmd) |
| 401 | + self.child = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid) |
347 | 402 |
|
348 | | - self.logger.info("Starting jtagd with command: " + cmd) |
349 | | - self.logger.info("Starting jtagd with LD_PRELOAD=" + lib_path + " HWSF_DEV=" + my_env["HWSF_DEV"]) |
350 | | - |
351 | | - self.child = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid, env=my_env) |
352 | | - |
353 | | - def release(self, *args, **kwargs): |
| 403 | + def _stop(self, start_params): |
354 | 404 | """Stop ``jtagd`` subprocess""" |
355 | 405 | assert self.child |
356 | | - |
357 | | - os.remove(self._get_custom_config_file_name()) |
358 | | - os.killpg(os.getpgid(self.child.pid), signal.SIGTERM) |
| 406 | + child = self.child |
359 | 407 | self.child = None |
360 | | - self.logger.info(f"stopped jtagd at {self.local.jtagd_port}") |
| 408 | + self.cfg_tempfile = None |
| 409 | + child.terminate() |
| 410 | + try: |
| 411 | + child.wait(2.0) |
| 412 | + except subprocess.TimeoutExpired: |
| 413 | + self.logger.warning("jtagd for %s still running after SIGTERM", |
| 414 | + self.local.device.sys_name) |
| 415 | + child.kill() |
| 416 | + child.wait(1.0) |
| 417 | + |
| 418 | + self.logger.info("stopped jtagd for %s on port %s", |
| 419 | + self.local.device.sys_name, self.jtagd_port) |
361 | 420 |
|
362 | 421 | def _get_params(self): |
363 | 422 | """Helper function to return parameters""" |
364 | 423 | return { |
365 | | - 'host': self.host, |
366 | | - 'jtagd_password': self.local.jtagd_password, |
367 | | - 'jtagd_port': self.local.jtagd_port, |
368 | | - 'jtagd_cmd': self.local.jtagd_cmd, |
369 | | - 'device_name': self.local.device_name, |
370 | | - 'device_port': self.local.device_port, |
| 424 | + **super()._get_params(), |
| 425 | + "jtagd_cmd": self.local.jtagd_cmd, |
| 426 | + "jtagd_port": self.jtagd_port, |
| 427 | + "jtagd_password": self.local.jtagd_password, |
| 428 | + "device_name": self.local.device_name, |
| 429 | + "device_port": self.local.device_port, |
| 430 | + "extra": { |
| 431 | + "jtag_conf": f"""Remote1 {{ |
| 432 | + Host = \"{self.host}:{self.jtagd_port}\"; |
| 433 | + Password = \"{self.local.jtagd_password}\"; |
| 434 | +}}""" |
| 435 | + } |
371 | 436 | } |
372 | 437 |
|
| 438 | + |
373 | 439 | exports["QuartusUSBJTAG"] = QuartusServerExport |
374 | 440 |
|
375 | 441 |
|
@@ -445,65 +511,6 @@ def _stop(self, start_params): |
445 | 511 | exports["XilinxUSBJTAG"] = VivadoHWServerExport |
446 | 512 |
|
447 | 513 |
|
448 | | -@attr.s(eq=False) |
449 | | -class NetworkInterfaceExport(ResourceExport): |
450 | | - """ResourceExport for a network interface""" |
451 | | - |
452 | | - def __attrs_post_init__(self): |
453 | | - super().__attrs_post_init__() |
454 | | - if self.cls == "NetworkInterface": |
455 | | - from ..resource.base import NetworkInterface |
456 | | - |
457 | | - self.local = NetworkInterface(target=None, name=None, **self.local_params) |
458 | | - elif self.cls == "USBNetworkInterface": |
459 | | - from ..resource.udev import USBNetworkInterface |
460 | | - |
461 | | - self.local = USBNetworkInterface(target=None, name=None, **self.local_params) |
462 | | - self.data["cls"] = "RemoteNetworkInterface" |
463 | | - |
464 | | - def _get_params(self): |
465 | | - """Helper function to return parameters""" |
466 | | - params = { |
467 | | - "host": self.host, |
468 | | - "ifname": self.local.ifname, |
469 | | - } |
470 | | - if self.cls == "USBNetworkInterface": |
471 | | - params["extra"] = { |
472 | | - "state": self.local.if_state, |
473 | | - } |
474 | | - |
475 | | - return params |
476 | | - |
477 | | - |
478 | | -exports["USBNetworkInterface"] = NetworkInterfaceExport |
479 | | -exports["NetworkInterface"] = NetworkInterfaceExport |
480 | | - |
481 | | - |
482 | | -@attr.s(eq=False) |
483 | | -class USBGenericExport(ResourceExport): |
484 | | - """ResourceExport for USB devices accessed directly from userspace""" |
485 | | - |
486 | | - def __attrs_post_init__(self): |
487 | | - super().__attrs_post_init__() |
488 | | - local_cls_name = self.cls |
489 | | - self.data["cls"] = f"Network{self.cls}" |
490 | | - from ..resource import udev |
491 | | - |
492 | | - local_cls = getattr(udev, local_cls_name) |
493 | | - self.local = local_cls(target=None, name=None, **self.local_params) |
494 | | - |
495 | | - def _get_params(self): |
496 | | - """Helper function to return parameters""" |
497 | | - return { |
498 | | - "host": self.host, |
499 | | - "busnum": self.local.busnum, |
500 | | - "devnum": self.local.devnum, |
501 | | - "path": self.local.path, |
502 | | - "vendor_id": self.local.vendor_id, |
503 | | - "model_id": self.local.model_id, |
504 | | - } |
505 | | - |
506 | | - |
507 | 514 | @attr.s(eq=False) |
508 | 515 | class USBSigrokExport(USBGenericExport): |
509 | 516 | """ResourceExport for USB devices accessed directly from userspace""" |
|
0 commit comments