mirror of https://github.com/m-labs/artiq.git
249 lines
9.5 KiB
Python
Executable File
249 lines
9.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# This script makes the following assumptions:
|
|
# * miniconda is installed remotely at ~/miniconda
|
|
# * misoc and artiq are installed remotely via conda
|
|
|
|
import sys
|
|
import argparse
|
|
import logging
|
|
import subprocess
|
|
import socket
|
|
import select
|
|
import threading
|
|
import os
|
|
import shutil
|
|
import re
|
|
|
|
from artiq.tools import verbosity_args, init_logger
|
|
from artiq.remoting import SSHClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_argparser():
|
|
parser = argparse.ArgumentParser(
|
|
description="ARTIQ core device development tool",
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
|
|
verbosity_args(parser)
|
|
|
|
parser.add_argument("-t", "--target", metavar="TARGET",
|
|
type=str, default="kc705_dds",
|
|
help="Target to build, one of: "
|
|
"kc705_dds kasli sayma_rtm sayma_amc_standalone "
|
|
"sayma_amc_drtio_master sayma_amc_drtio_satellite")
|
|
parser.add_argument("-H", "--host",
|
|
type=str, default="lab.m-labs.hk",
|
|
help="SSH host where the development board is located")
|
|
parser.add_argument('-b', "--board",
|
|
type=str, default="{boardtype}-1",
|
|
help="Board to connect to on the development SSH host")
|
|
parser.add_argument("-d", "--device",
|
|
type=str, default="{board}.{host}",
|
|
help="Address or domain corresponding to the development board")
|
|
parser.add_argument("-s", "--serial",
|
|
type=str, default="/dev/ttyUSB_{board}",
|
|
help="TTY device corresponding to the development board")
|
|
parser.add_argument("-l", "--lockfile",
|
|
type=str, default="/run/boards/{board}",
|
|
help="The lockfile to be acquired for the duration of the actions")
|
|
parser.add_argument("-w", "--wait", action="store_true",
|
|
help="Wait for the board to unlock instead of aborting the actions")
|
|
|
|
parser.add_argument("actions", metavar="ACTION",
|
|
type=str, default=[], nargs="+",
|
|
help="actions to perform, sequence of: "
|
|
"build clean reset flash flash+log connect hotswap")
|
|
|
|
return parser
|
|
|
|
|
|
def main():
|
|
args = get_argparser().parse_args()
|
|
init_logger(args)
|
|
if args.verbose == args.quiet == 0:
|
|
logging.getLogger().setLevel(logging.INFO)
|
|
|
|
def build_dir(*path, target=args.target):
|
|
return os.path.join("/tmp", target, *path)
|
|
|
|
build_args = []
|
|
if args.target == "kc705_dds":
|
|
boardtype, firmware = "kc705", "runtime"
|
|
elif args.target == "sayma_amc_standalone":
|
|
boardtype, firmware = "sayma_amc", "runtime"
|
|
build_args += ["--rtm-csr-csv", build_dir("sayma_rtm_csr.csv", target="sayma_rtm")]
|
|
elif args.target == "sayma_amc_drtio_master":
|
|
boardtype, firmware = "sayma_amc", "runtime"
|
|
elif args.target == "sayma_amc_drtio_satellite":
|
|
boardtype, firmware = "sayma_amc", "satman"
|
|
elif args.target == "sayma_rtm":
|
|
boardtype, firmware = "sayma_rtm", None
|
|
else:
|
|
raise NotImplementedError("unknown target {}".format(args.target))
|
|
|
|
board = args.board.format(boardtype=boardtype)
|
|
device = args.device.format(board=board, host=args.host)
|
|
lockfile = args.lockfile.format(board=board)
|
|
serial = args.serial.format(board=board)
|
|
|
|
client = SSHClient(args.host)
|
|
|
|
flock_acquired = False
|
|
flock_file = None # GC root
|
|
def lock():
|
|
nonlocal flock_acquired
|
|
nonlocal flock_file
|
|
|
|
if not flock_acquired:
|
|
fuser_args = ["fuser", "-u", lockfile]
|
|
fuser = client.spawn_command(fuser_args)
|
|
fuser_file = fuser.makefile('r')
|
|
fuser_match = re.search(r"\((.+?)\)", fuser_file.readline())
|
|
if fuser_match and fuser_match.group(1) == os.getenv("USER"):
|
|
logger.info("Lock already acquired by {}".format(os.getenv("USER")))
|
|
flock_acquired = True
|
|
return
|
|
|
|
logger.info("Acquiring device lock")
|
|
flock_args = ["flock"]
|
|
if not args.wait:
|
|
flock_args.append("--nonblock")
|
|
flock_args += ["--verbose", lockfile]
|
|
flock_args += ["sleep", "86400"]
|
|
|
|
flock = client.spawn_command(flock_args, get_pty=True)
|
|
flock_file = flock.makefile('r')
|
|
while not flock_acquired:
|
|
line = flock_file.readline()
|
|
if not line:
|
|
break
|
|
logger.debug(line.rstrip())
|
|
if line.startswith("flock: executing"):
|
|
flock_acquired = True
|
|
elif line.startswith("flock: failed"):
|
|
logger.error("Failed to get lock")
|
|
sys.exit(1)
|
|
|
|
def flash(*steps):
|
|
flash_args = ["artiq_flash"]
|
|
for _ in range(args.verbose):
|
|
flash_args.append("-v")
|
|
flash_args += ["-H", args.host, "-t", boardtype]
|
|
flash_args += ["--srcbuild", build_dir()]
|
|
flash_args += ["--preinit-command", "source /var/boards/{}".format(board)]
|
|
flash_args += steps
|
|
subprocess.check_call(flash_args)
|
|
|
|
for action in args.actions:
|
|
if action == "build":
|
|
logger.info("Building target")
|
|
try:
|
|
subprocess.check_call([
|
|
"python3", "-m", "artiq.gateware.targets." + args.target,
|
|
"--no-compile-gateware",
|
|
*build_args,
|
|
"--output-dir", build_dir()])
|
|
except subprocess.CalledProcessError:
|
|
logger.error("Build failed")
|
|
sys.exit(1)
|
|
|
|
elif action == "clean":
|
|
logger.info("Cleaning build directory")
|
|
shutil.rmtree(build_dir, ignore_errors=True)
|
|
|
|
elif action == "reset":
|
|
lock()
|
|
|
|
logger.info("Resetting device")
|
|
flash("start")
|
|
|
|
elif action == "flash":
|
|
lock()
|
|
|
|
logger.info("Flashing and booting firmware")
|
|
flash("proxy", "bootloader", "firmware", "start")
|
|
|
|
elif action == "flash+log":
|
|
lock()
|
|
|
|
logger.info("Flashing firmware")
|
|
flash("proxy", "bootloader", "firmware")
|
|
|
|
flterm = client.spawn_command(["flterm", serial, "--output-only"])
|
|
logger.info("Booting firmware")
|
|
flash("start")
|
|
client.drain(flterm)
|
|
|
|
elif action == "connect":
|
|
lock()
|
|
|
|
transport = client.get_transport()
|
|
transport.set_keepalive(30)
|
|
|
|
def forwarder(local_stream, remote_stream):
|
|
try:
|
|
while True:
|
|
r, _, _ = select.select([local_stream, remote_stream], [], [])
|
|
if local_stream in r:
|
|
data = local_stream.recv(65535)
|
|
if data == b"":
|
|
break
|
|
remote_stream.sendall(data)
|
|
if remote_stream in r:
|
|
data = remote_stream.recv(65535)
|
|
if data == b"":
|
|
break
|
|
local_stream.sendall(data)
|
|
except Exception as err:
|
|
logger.error("Cannot forward on port %s: %s", port, repr(err))
|
|
local_stream.close()
|
|
remote_stream.close()
|
|
|
|
def listener(port):
|
|
listener = socket.socket()
|
|
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
listener.bind(('localhost', port))
|
|
listener.listen(8)
|
|
while True:
|
|
local_stream, peer_addr = listener.accept()
|
|
logger.info("Accepting %s:%s and opening SSH channel to %s:%s",
|
|
*peer_addr, device, port)
|
|
try:
|
|
remote_stream = \
|
|
transport.open_channel('direct-tcpip', (device, port), peer_addr)
|
|
except Exception:
|
|
logger.exception("Cannot open channel on port %s", port)
|
|
continue
|
|
|
|
thread = threading.Thread(target=forwarder, args=(local_stream, remote_stream),
|
|
name="forward-{}".format(port), daemon=True)
|
|
thread.start()
|
|
|
|
ports = [1380, 1381, 1382, 1383]
|
|
for port in ports:
|
|
thread = threading.Thread(target=listener, args=(port,),
|
|
name="listen-{}".format(port), daemon=True)
|
|
thread.start()
|
|
|
|
logger.info("Forwarding ports {} to core device and logs from core device"
|
|
.format(", ".join(map(str, ports))))
|
|
client.run_command(["flterm", serial, "--output-only"])
|
|
|
|
elif action == "hotswap":
|
|
logger.info("Hotswapping firmware")
|
|
try:
|
|
subprocess.check_call(["artiq_coreboot", "hotswap",
|
|
build_dir("software", firmware, firmware + ".bin")])
|
|
except subprocess.CalledProcessError:
|
|
logger.error("Build failed")
|
|
sys.exit(1)
|
|
|
|
else:
|
|
logger.error("Unknown action {}".format(action))
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|