blob: 9ada58f310eb6c35e74a778162ca0ebd2ccc5035 [file]
#!/usr/bin/env python3
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0
"""
AST1060 EVB hardware interaction layer.
Handles GPIO reset sequences, firmware upload via UART bootloader, and raw
UART byte streaming. All configuration is received as CLI arguments from
test_runner.py. Raw UART bytes are written to stdout; diagnostics go to
stderr. Runs locally or is SCP'd to the Pi for remote test execution.
"""
import argparse
import subprocess
import sys
import threading
import time
from contextlib import nullcontext
from pathlib import Path
try:
import serial
except ImportError:
print(
"Error: pyserial not installed. Install with: pip install pyserial",
file=sys.stderr,
)
sys.exit(1)
def _gpio_set(pin: int, state: str) -> None:
subprocess.run(["pinctrl", "set", str(pin), "op"] + state.split(), check=True)
def _sequence_to_fwspick_mode(
srst_pin: int, fwspick_pin: int, port: serial.Serial
) -> None:
_gpio_set(srst_pin, "dl")
time.sleep(0.1)
port.timeout = 0.1
port.read(4096)
_gpio_set(fwspick_pin, "pn dh")
time.sleep(1)
_gpio_set(srst_pin, "dh")
time.sleep(1)
def _wait_for_uart_ready(port: serial.Serial, timeout: int = 30) -> bool:
deadline = time.time() + timeout
buf = b""
port.timeout = 0.1
while time.time() < deadline:
data = port.read(1024)
if data:
buf += data
sys.stderr.buffer.write(data)
sys.stderr.buffer.flush()
if b"U" in buf:
return True
print("Timeout waiting for UART bootloader ready signal", file=sys.stderr)
return False
def _upload_firmware(port: serial.Serial, firmware_path: Path) -> None:
data = firmware_path.read_bytes()
size = len(data)
aligned = (size + 3) & ~3
port.write(aligned.to_bytes(4, "little"))
chunk_size = 1024
for i in range(0, size, chunk_size):
port.write(data[i : i + chunk_size])
port.flush()
time.sleep(0.01)
padding = aligned - size
if padding:
port.write(bytes(padding))
print(f"Uploaded {size} bytes ({padding} bytes padding)", file=sys.stderr)
_stdout_lock = threading.Lock()
_SUCCESS_SENTINEL = b"TEST_RESULT:PASS"
_FAILURE_SENTINELS = [b"TEST_RESULT:FAIL", b"panic"]
def _stream_uart(port: serial.Serial, timeout: int, lock=None) -> bool:
port.timeout = 1.0
deadline = time.time() + timeout if timeout else None
buf = b""
while True:
if deadline and time.time() >= deadline:
print("Timeout waiting for test result sentinel", file=sys.stderr)
return False
data = port.read(1024)
if data:
try:
with lock or nullcontext():
sys.stdout.buffer.write(data)
sys.stdout.buffer.flush()
except (BrokenPipeError, OSError):
return False
buf += data
if _SUCCESS_SENTINEL in buf:
return True
for s in _FAILURE_SENTINELS:
if s in buf:
return False
buf = buf[-256:]
def _run_paired(args, firmware_path: Path, slave_firmware_path: Path) -> bool:
try:
port_b = serial.Serial(
args.slave_uart_device,
baudrate=args.baudrate,
timeout=1.0,
write_timeout=1.0,
)
except serial.SerialException as e:
print(f"Error: could not open {args.slave_uart_device}: {e}", file=sys.stderr)
return False
try:
port_a = serial.Serial(
args.uart_device,
baudrate=args.baudrate,
timeout=1.0,
write_timeout=1.0,
)
except serial.SerialException as e:
port_b.close()
print(f"Error: could not open {args.uart_device}: {e}", file=sys.stderr)
return False
try:
_sequence_to_fwspick_mode(args.slave_srst_pin, args.slave_fwspick_pin, port_b)
if not _wait_for_uart_ready(port_b):
return False
_upload_firmware(port_b, slave_firmware_path)
_sequence_to_fwspick_mode(args.srst_pin, args.fwspick_pin, port_a)
if not _wait_for_uart_ready(port_a):
return False
_upload_firmware(port_a, firmware_path)
results = [None, None]
def _monitor(idx, port):
results[idx] = _stream_uart(port, args.timeout, _stdout_lock)
threads = [
threading.Thread(target=_monitor, args=(0, port_a)),
threading.Thread(target=_monitor, args=(1, port_b)),
]
for t in threads:
t.start()
for t in threads:
t.join()
return bool(results[0] and results[1])
except KeyboardInterrupt:
return False
finally:
port_a.close()
port_b.close()
def main() -> int:
parser = argparse.ArgumentParser(
description="AST1060 EVB hardware layer: GPIO, firmware upload, UART stream"
)
parser.add_argument(
"uart_device",
help="Serial port device path (e.g. /dev/ttyUSB0)",
)
parser.add_argument(
"firmware",
nargs="?",
help="Firmware binary to upload. Not required with --stream-only",
)
parser.add_argument(
"--srst-pin",
type=int,
required=True,
help="BCM GPIO pin connected to the AST1060 SRST line",
)
parser.add_argument(
"--fwspick-pin",
type=int,
required=True,
help="BCM GPIO pin connected to the AST1060 FWSPICK line",
)
parser.add_argument(
"--baudrate",
type=int,
required=True,
help="Serial port baud rate, must match firmware UART initialisation",
)
parser.add_argument(
"--timeout",
type=int,
default=600,
help="Seconds to wait for a result sentinel (0 = no timeout, default: 600)",
)
parser.add_argument(
"--stream-only",
action="store_true",
help="Skip GPIO sequences and firmware upload; stream raw UART bytes only",
)
parser.add_argument(
"--slave-firmware",
default=None,
help="Slave firmware binary. When present, enables paired two-device mode.",
)
parser.add_argument(
"--slave-uart-device",
default=None,
help="Serial port for device B (e.g. /dev/ttyUSB1)",
)
parser.add_argument(
"--slave-srst-pin",
type=int,
default=None,
help="BCM GPIO pin connected to device B SRST",
)
parser.add_argument(
"--slave-fwspick-pin",
type=int,
default=None,
help="BCM GPIO pin connected to device B FWSPICK",
)
args = parser.parse_args()
if not args.stream_only:
if not args.firmware:
parser.error("firmware is required unless --stream-only is set")
firmware_path = Path(args.firmware)
if not firmware_path.exists():
print(f"Error: firmware not found: {firmware_path}", file=sys.stderr)
return 1
else:
firmware_path = None
if args.slave_firmware:
missing = [
name
for name, val in [
("--slave-uart-device", args.slave_uart_device),
("--slave-srst-pin", args.slave_srst_pin),
("--slave-fwspick-pin", args.slave_fwspick_pin),
]
if val is None
]
if missing:
parser.error(f"paired mode requires: {', '.join(missing)}")
slave_firmware_path = Path(args.slave_firmware)
if not slave_firmware_path.exists():
print(
f"Error: slave firmware not found: {slave_firmware_path}",
file=sys.stderr,
)
return 1
return 0 if _run_paired(args, firmware_path, slave_firmware_path) else 1
try:
port = serial.Serial(
args.uart_device,
baudrate=args.baudrate,
timeout=1.0,
write_timeout=1.0,
)
except serial.SerialException as e:
print(f"Error: could not open {args.uart_device}: {e}", file=sys.stderr)
return 1
result = False
try:
if not args.stream_only:
_sequence_to_fwspick_mode(args.srst_pin, args.fwspick_pin, port)
if not _wait_for_uart_ready(port):
return 1
_upload_firmware(port, firmware_path)
result = _stream_uart(port, args.timeout)
except KeyboardInterrupt:
pass
finally:
port.close()
return 0 if result else 1
if __name__ == "__main__":
sys.exit(main())