artiq/artiq/frontend/artiq_devtool.py

217 lines
8.6 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2016-11-11 04:25:15 +08:00
# This script makes the following assumptions:
# * miniconda is installed remotely at ~/miniconda
# * misoc and artiq are installed remotely via conda
import sys
import argparse
2017-11-26 23:17:35 +08:00
import logging
2016-11-11 04:25:15 +08:00
import subprocess
import socket
import select
import threading
import os
import shutil
2016-11-11 04:25:15 +08:00
2017-06-25 15:04:29 +08:00
from artiq.tools import verbosity_args, init_logger, logger, SSHClient
2016-11-11 04:25:15 +08:00
def get_argparser():
parser = argparse.ArgumentParser(description="ARTIQ core device "
"development tool")
verbosity_args(parser)
2017-11-26 23:17:35 +08:00
parser.add_argument("-H", "--host", metavar="HOSTNAME",
2016-11-11 04:25:15 +08:00
type=str, default="lab.m-labs.hk",
help="SSH host where the development board is located")
2017-11-26 23:17:35 +08:00
parser.add_argument("-D", "--device", metavar="HOSTNAME",
type=str, default="kc705.lab.m-labs.hk",
help="address or domain corresponding to the development board")
2017-11-26 23:17:35 +08:00
parser.add_argument("-s", "--serial", metavar="PATH",
2017-04-13 16:26:48 +08:00
type=str, default="/dev/ttyUSB_kc705",
2016-11-11 04:25:15 +08:00
help="TTY device corresponding to the development board")
2017-11-26 23:17:35 +08:00
parser.add_argument("-l", "--lockfile", metavar="PATH",
type=str, default="/run/boards/kc705",
help="The lockfile to be acquired for the duration of the script")
parser.add_argument("-t", "--target", metavar="TARGET",
type=str, default="kc705_dds",
help="Target to build, one of: "
2017-04-19 17:38:24 +08:00
"kc705_dds kc705_drtio_master kc705_drtio_satellite")
2017-11-26 23:17:35 +08:00
parser.add_argument("-c", "--config", metavar="PATH",
2017-04-13 16:26:48 +08:00
type=str, default="openocd-kc705.cfg",
help="OpenOCD configuration file corresponding to the development board")
2016-11-11 04:25:15 +08:00
parser.add_argument("actions", metavar="ACTION",
type=str, default=[], nargs="+",
help="actions to perform, sequence of: "
2017-03-07 22:37:08 +08:00
"build reset boot boot+log connect hotswap clean")
2016-11-11 04:25:15 +08:00
return parser
def main():
args = get_argparser().parse_args()
init_logger(args)
2017-11-26 23:17:35 +08:00
if args.verbose == args.quiet == 0:
logging.getLogger().setLevel(logging.INFO)
2016-11-11 04:25:15 +08:00
2017-04-19 17:38:24 +08:00
if args.target == "kc705_dds" or args.target == "kc705_drtio_master":
firmware = "runtime"
elif args.target == "kc705_drtio_satellite":
firmware = "satman"
else:
raise NotImplementedError("unknown target {}".format(args.target))
2017-06-25 15:04:29 +08:00
client = SSHClient(args.host)
substs = {
"env": "bash -c 'export PATH=$HOME/miniconda/bin:$PATH; exec $0 $*' ",
"serial": args.serial,
"firmware": firmware,
}
2016-11-11 04:25:15 +08:00
2017-11-26 23:17:35 +08:00
lock_acquired = False
def lock():
nonlocal lock_acquired
if not lock_acquired:
logger.info("Acquiring device lock")
flock = client.spawn_command("flock --verbose --nonblock {} /bin/sleep 86400"
.format(args.lockfile),
get_pty=True)
flock_file = flock.makefile('r')
while not lock_acquired:
line = flock_file.readline()
if not line:
break
logger.debug(line.rstrip())
if line.startswith("flock: executing"):
lock_acquired = True
elif line.startswith("flock: failed"):
logger.error("Failed to get lock")
sys.exit(1)
2016-11-11 04:25:15 +08:00
for action in args.actions:
if action == "build":
logger.info("Building firmware")
2016-11-13 04:11:55 +08:00
try:
subprocess.check_call(["python3",
"-m", "artiq.gateware.targets." + args.target,
"--no-compile-gateware",
"--output-dir",
"/tmp/{target}".format(target=args.target)])
2016-11-13 04:11:55 +08:00
except subprocess.CalledProcessError:
logger.error("Build failed")
sys.exit(1)
2016-11-11 04:25:15 +08:00
elif action == "clean":
logger.info("Cleaning build directory")
target_dir = "/tmp/{target}".format(target=args.target)
if os.path.isdir(target_dir):
shutil.rmtree(target_dir)
2017-03-07 22:37:08 +08:00
elif action == "reset":
2017-11-26 23:17:35 +08:00
lock()
2017-03-07 22:37:08 +08:00
logger.info("Resetting device")
2017-06-25 15:04:29 +08:00
client.run_command(
2017-03-07 22:37:08 +08:00
"{env} artiq_flash start" +
2017-06-25 15:04:29 +08:00
(" --target-file " + args.config if args.config else ""),
**substs)
2017-03-07 22:37:08 +08:00
elif action == "boot" or action == "boot+log":
2017-11-26 23:17:35 +08:00
lock()
logger.info("Uploading firmware")
2017-06-25 15:04:29 +08:00
client.get_sftp().put("/tmp/{target}/software/{firmware}/{firmware}.bin"
.format(target=args.target, firmware=firmware),
"{tmp}/{firmware}.bin"
.format(tmp=client.tmp, firmware=firmware))
2016-11-11 04:25:15 +08:00
logger.info("Booting firmware")
2017-06-25 15:04:29 +08:00
flterm = client.spawn_command(
"{env} python3 flterm.py {serial} " +
2017-06-25 15:04:29 +08:00
"--kernel {tmp}/{firmware}.bin " +
("--upload-only" if action == "boot" else "--output-only"),
**substs)
artiq_flash = client.spawn_command(
"{env} artiq_flash start" +
2017-06-25 15:04:29 +08:00
(" --target-file " + args.config if args.config else ""),
**substs)
client.drain(flterm)
2016-11-11 04:25:15 +08:00
elif action == "connect":
2017-11-26 23:17:35 +08:00
lock()
transport = client.get_transport()
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):
2016-11-11 04:25:15 +08:00
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
2016-11-11 04:25:15 +08:00
listener.bind(('localhost', port))
listener.listen(8)
2016-11-11 04:25:15 +08:00
while True:
local_stream, peer_addr = listener.accept()
logger.info("Accepting %s:%s and opening SSH channel to %s:%s",
*peer_addr, args.device, port)
try:
remote_stream = \
transport.open_channel('direct-tcpip', (args.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()
2016-11-11 04:25:15 +08:00
2017-11-26 23:17:35 +08:00
ports = [1380, 1381, 1382, 1383]
for port in ports:
thread = threading.Thread(target=listener, args=(port,),
name="listen-{}".format(port), daemon=True)
2016-11-11 04:25:15 +08:00
thread.start()
2017-11-26 23:17:35 +08:00
logger.info("Forwarding ports {} to core device and logs from core device"
.format(", ".join(map(str, ports))))
2017-06-25 15:04:29 +08:00
client.run_command(
"{env} python3 flterm.py {serial} --output-only",
**substs)
2016-11-11 04:25:15 +08:00
2017-03-07 14:49:02 +08:00
elif action == "hotswap":
logger.info("Hotswapping firmware")
try:
subprocess.check_call(["python3",
"-m", "artiq.frontend.artiq_coreboot", "hotswap",
2017-03-07 14:49:02 +08:00
"/tmp/{target}/software/{firmware}/{firmware}.bin"
.format(target=args.target, firmware=firmware)])
except subprocess.CalledProcessError:
logger.error("Build failed")
sys.exit(1)
2016-11-11 04:25:15 +08:00
else:
logger.error("Unknown action {}".format(action))
sys.exit(1)
if __name__ == "__main__":
main()