From 0a207aa9fa335acddfa8846a4b9710feba8d7663 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Wed, 19 Nov 2025 15:19:08 -0800 Subject: [PATCH 1/9] ci: Add hardware-in-the-loop testing workflow Add GitHub Actions workflow for automated hardware testing on AST1060. The workflow runs in two stages: 1. Precommit checks on ubuntu-22.04: - Verify Cargo.lock - Run cargo xtask precommit (format/lint/build) - Upload bloat reports 2. Hardware functional tests on self-hosted runner with AST1060: - Build firmware with test features (hmac, hash, rsa, ecdsa) - Generate UART boot image with proper 4-byte size header - Upload firmware via UART following AST1060 boot protocol - Monitor test execution and parse PASS/FAIL/SKIP results - Upload test logs and artifacts The workflow supports manual triggering with test suite selection and only runs hardware tests if precommit checks pass. --- .github/workflows/hardware-test.yml | 423 ++++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 .github/workflows/hardware-test.yml diff --git a/.github/workflows/hardware-test.yml b/.github/workflows/hardware-test.yml new file mode 100644 index 0000000..46d114a --- /dev/null +++ b/.github/workflows/hardware-test.yml @@ -0,0 +1,423 @@ +# Licensed under the Apache-2.0 license + +# Hardware-in-the-Loop Testing Workflow +# +# This workflow runs in two stages: +# +# Stage 1: precommit-checks (ubuntu-22.04) +# • Checkout code +# • Install packages +# • Verify Cargo.lock +# • Run cargo xtask precommit (format/lint/build) +# • Upload bloat reports +# +# Stage 2: hardware-functional-tests (self-hosted with AST1060) +# • Checkout code +# • Install Rust toolchain +# • Build firmware with test features +# • Generate binary image +# • Power off board +# • Upload firmware via UART (following AST1060 UART boot flow) +# • Power cycle for normal boot +# • Monitor test execution via UART +# • Parse results (PASS/FAIL/SKIP) +# • Upload test logs +# +# Hardware tests only run if precommit checks pass (needs: precommit-checks) +# Can be triggered manually with test suite selection (hmac, hash, rsa, ecdsa, all) +# +# Workflow Diagram: +# +# ┌─────────────────────────────────────────────┐ +# │ Stage 1: precommit-checks │ +# │ Runner: ubuntu-22.04 (GitHub-hosted) │ +# ├─────────────────────────────────────────────┤ +# │ • Verify Cargo.lock │ +# │ • cargo xtask precommit │ +# │ • Upload bloat reports │ +# └──────────────────┬──────────────────────────┘ +# │ +# │ (only if passes) +# ▼ +# ┌─────────────────────────────────────────────┐ +# │ Stage 2: hardware-functional-tests │ +# │ Runner: self-hosted (with AST1060) │ +# ├─────────────────────────────────────────────┤ +# │ • Build firmware (release profile) │ +# │ • Generate binary │ +# │ • UART upload to AST1060 │ +# │ • Monitor test execution │ +# │ • Parse PASS/FAIL/SKIP results │ +# │ • Upload test logs │ +# └─────────────────────────────────────────────┘ +# +# AST1060 UART Upload Flow: +# 1. Power off device +# 2. Enable FWSPICK strap (J156 pins 1-2 or SW6 pin 3) +# 3. Execute uart_fw_py command +# 4. Power on device (while uart_fw is running) +# 5. uart_fw uploads firmware to flash +# 6. Remove FWSPICK strap +# 7. Power cycle for normal boot +# 8. Monitor UART output for test results + +name: Hardware-in-the-Loop Functional Tests + +on: + push: + branches: + - main + - ci-hil + pull_request: + branches: + - main + workflow_dispatch: + inputs: + test_suite: + description: 'Test suite to run (hmac, hash, rsa, ecdsa, all)' + required: false + default: 'all' + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + EXTRA_CARGO_CONFIG: "target.'cfg(all())'.rustflags = [\"-Dwarnings\"]" + UART_BAUDRATE: 921600 + UART_PORT: /dev/ttyUSB0 # Adjust for your CI runner + AST1060_PLATFORM: ast1060 + +jobs: + # First stage: Build checks on standard CI runner + precommit-checks: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install packages + run: | + sudo apt-get update -qy + sudo apt-get install -qy build-essential curl gcc-multilib gcc-riscv64-unknown-elf git + + - name: Install cargo-bloat + run: cargo install cargo-bloat + + - name: Verify Cargo.lock is up to date + run: | + cargo tree --locked > /dev/null || ( + echo "Please update Cargo.lock" + cargo tree + git diff Cargo.lock + exit 1 + ) + + - name: Run precommit checks (build/format/lint) + run: | + cargo --config "$EXTRA_CARGO_CONFIG" xtask precommit + + - name: Upload binary size reports + uses: actions/upload-artifact@v4 + if: always() && hashFiles('target/bloat-reports/*') != '' + with: + name: bloat-reports + path: target/bloat-reports/ + retention-days: 30 + + # Second stage: Hardware tests on self-hosted runner with AST1060 + hardware-functional-tests: + needs: precommit-checks + runs-on: self-hosted # Must be self-hosted runner with AST1060 hardware + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2024-09-17 + target: thumbv7em-none-eabihf + override: true + components: rust-src, clippy + + - name: Install dependencies + run: | + # Install required tools + sudo apt-get update -qy + sudo apt-get install -qy build-essential python3 python3-serial gcc-arm-none-eabi + + # Install UART firmware upload tool (adjust path as needed) + # Assuming uart_fw_py is in your repo or available + if [ ! -f ./scripts/uart_fw_py ]; then + echo "Error: uart_fw_py not found in scripts/" + exit 1 + fi + chmod +x ./scripts/uart_fw_py + + - name: Build functional test firmware + run: | + # Determine test suite features + SUITE="${{ github.event.inputs.test_suite || 'all' }}" + + if [ "$SUITE" = "all" ]; then + FEATURES="test-hmac,test-hash,test-rsa,test-ecdsa" + else + FEATURES="test-$SUITE" + fi + + echo "Building with features: $FEATURES" + cargo build --release --target thumbv7em-none-eabihf --features "$FEATURES" + + - name: Generate binary image + run: | + # Convert ELF to raw binary + arm-none-eabi-objcopy \ + -O binary \ + target/thumbv7em-none-eabihf/release/aspeed-ddk \ + target/functional-tests-raw.bin + + # Wrap with UART boot header (4-byte size prefix) + ./scripts/gen_uart_booting_image.sh \ + target/functional-tests-raw.bin \ + target/functional-tests.bin + + # Verify binary sizes + RAW_SIZE=$(stat -c%s target/functional-tests-raw.bin) + UART_SIZE=$(stat -c%s target/functional-tests.bin) + echo "Raw binary size: $RAW_SIZE bytes" + echo "UART boot image size: $UART_SIZE bytes (includes 4-byte header)" + + if [ $RAW_SIZE -gt 1048576 ]; then + echo "Error: Binary too large for 1MB flash" + exit 1 + fi + + - name: Prepare hardware - Power off + run: | + echo "Powering off AST1060 board..." + # Add your power control command here (e.g., PDU control, GPIO, etc.) + # Example: ./scripts/power_control.sh off + sleep 2 + + - name: Check UART port availability + run: | + if [ ! -e "$UART_PORT" ]; then + echo "Error: UART port $UART_PORT not found" + echo "Available ports:" + ls -la /dev/ttyUSB* /dev/ttyACM* 2>/dev/null || echo "No USB serial ports found" + exit 1 + fi + + # Ensure port is not in use + sudo fuser -k $UART_PORT 2>/dev/null || true + sleep 1 + + - name: Verify FWSPICK strap (manual check) + run: | + echo "======================================" + echo "HARDWARE CHECK REQUIRED:" + echo "Ensure FWSPICK is connected to VCC_3V3" + echo " - AST1060 EVB: J156 pins 1-2 connected" + echo " - DC-SCM: SW6 pin 3 ON" + echo "======================================" + + # In a real CI setup, you'd have automated strap control + # For now, this is a documentation step + # You could add a GPIO-controlled relay here + + - name: Upload firmware via UART + run: | + echo "Starting UART firmware upload..." + echo "Command will wait for power-on..." + + # Run UART upload in background + sudo ./scripts/uart_fw_py \ + --platform $AST1060_PLATFORM \ + --spi 0 \ + --cs 0 \ + --comport $UART_PORT \ + --baudrate $UART_BAUDRATE \ + --input_image target/functional-tests.bin \ + --erase_all & + + UART_PID=$! + + # Give uart_fw time to initialize + sleep 3 + + # Power on the board + echo "Powering on AST1060 board..." + # Add your power control command here + # Example: ./scripts/power_control.sh on + + # Wait for UART upload to complete + wait $UART_PID + UPLOAD_STATUS=$? + + if [ $UPLOAD_STATUS -ne 0 ]; then + echo "Error: UART firmware upload failed with exit code $UPLOAD_STATUS" + exit 1 + fi + + echo "Firmware upload completed successfully" + + - name: Remove FWSPICK strap (manual or automated) + run: | + echo "======================================" + echo "HARDWARE ACTION REQUIRED:" + echo "Remove FWSPICK connection" + echo " - AST1060 EVB: Disconnect J156 pins" + echo " - DC-SCM: Turn OFF SW6 pin 3" + echo "======================================" + + # In automated setup, control via GPIO/relay + sleep 2 + + - name: Power cycle board for normal boot + run: | + echo "Power cycling for normal boot..." + + # Power off + # ./scripts/power_control.sh off + sleep 2 + + # Power on + # ./scripts/power_control.sh on + sleep 5 + + echo "Board booted in normal mode" + + - name: Monitor test execution via UART + run: | + echo "Monitoring test execution..." + + # Capture UART output for test results + timeout 60 sudo python3 - <<'EOF' +import serial +import sys +import re + +port = "${{ env.UART_PORT }}" +baudrate = 115200 # Tests run at standard baud after boot + +try: + ser = serial.Serial(port, baudrate, timeout=1) + print(f"Listening on {port} at {baudrate} baud...") + + test_results = { + 'passed': 0, + 'failed': 0, + 'skipped': 0 + } + + output_buffer = [] + + while True: + line = ser.readline().decode('utf-8', errors='ignore').strip() + if not line: + continue + + output_buffer.append(line) + print(line) + + # Parse test results + if 'PASS' in line: + test_results['passed'] += 1 + elif 'FAIL' in line: + test_results['failed'] += 1 + elif 'SKIP' in line: + test_results['skipped'] += 1 + + # Check for completion + if 'Test Suite' in line and 'Complete' in line: + break + if 'panic' in line.lower(): + print("ERROR: Panic detected!") + sys.exit(1) + + # Print summary + print("\n" + "="*50) + print("Test Results Summary:") + print(f" Passed: {test_results['passed']}") + print(f" Failed: {test_results['failed']}") + print(f" Skipped: {test_results['skipped']}") + print("="*50) + + # Exit with appropriate code + if test_results['failed'] > 0: + sys.exit(1) + else: + sys.exit(0) + +except serial.SerialException as e: + print(f"Serial port error: {e}") + sys.exit(1) +except KeyboardInterrupt: + print("Monitoring interrupted") + sys.exit(1) +finally: + if 'ser' in locals(): + ser.close() +EOF + + TEST_STATUS=$? + + if [ $TEST_STATUS -ne 0 ]; then + echo "Tests FAILED or monitoring error" + exit 1 + fi + + echo "All tests PASSED" + + - name: Save test logs + if: always() + run: | + # Save logs for analysis + mkdir -p test-results + + # Capture final UART output + timeout 5 sudo python3 -c " +import serial +import sys +with serial.Serial('${{ env.UART_PORT }}', 115200, timeout=1) as ser: + for _ in range(50): + line = ser.readline().decode('utf-8', errors='ignore') + if line: + print(line, end='') +" > test-results/uart-output.log 2>&1 || true + + echo "Test logs saved to test-results/" + + - name: Upload test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ github.run_number }} + path: | + test-results/ + target/functional-tests.bin + retention-days: 30 + + - name: Cleanup + if: always() + run: | + # Ensure board is in normal state + # ./scripts/power_control.sh off + sudo fuser -k $UART_PORT 2>/dev/null || true + echo "Cleanup completed" + + - name: Report status + if: always() + run: | + if [ ${{ job.status }} == 'success' ]; then + echo "✅ Hardware tests PASSED" + else + echo "❌ Hardware tests FAILED" + exit 1 + fi From 3794134056dbde04fdc7aae8384a622f3fbb42c8 Mon Sep 17 00:00:00 2001 From: Will Marone Date: Thu, 20 Nov 2025 18:59:03 -0800 Subject: [PATCH 2/9] Update setup scripts and instructions - Added instructions on how to configure the test host environment. - Added scripts required to package the binary and kick off the test. --- README.md | 36 +++++ scripts/ast-bin-create-and-package.sh | 205 ++++++++++++++++++++++++++ scripts/ast-uart-fw.sh | 18 +++ 3 files changed, 259 insertions(+) create mode 100644 scripts/ast-bin-create-and-package.sh create mode 100755 scripts/ast-uart-fw.sh diff --git a/README.md b/README.md index 200a6f6..d8e23a7 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,39 @@ $ cargo build --release Hello, world! aspeed_ddk! ``` + +## Running the app on Hardware + +### Host Platform + +The recommended host platform is a Raspberry Pi, per ASpeed. Connecting two GPIO from the Pi to SRST pin 1 and FWSPICK pin 2 will allow the upload script to manage UART boot state and device ready. Check the upload script for the correct pins. + +### TIO + +TIO is used as a multiplexer to allow observing returned data streams while other applications write. + +1. Clone https://github.com/tio/tio.git and follow its build instructions. Ensure HEAD is 3af4c559 or newer. + +### Run + +1. Run TIO: + ``` + ./build/src/tio -S unix:/tmp/tio-socket-ast1060 + ``` + +2. Run the upload script + ``` + aspeed-rust $ ./scripts/ast-uart-fw.sh UNIX-CONNECT:/tmp/tio-socket-ast1060 uart_ast10x0.bin + ``` + The upload may take a while due to the size of the binary. On the TIO terminal you should see: + + ``` + UP0c0 + Hello, world!! + + + aspeed_ddk::hash::Sha256... + ``` + + As the test begins executing. + diff --git a/scripts/ast-bin-create-and-package.sh b/scripts/ast-bin-create-and-package.sh new file mode 100644 index 0000000..de6c3b4 --- /dev/null +++ b/scripts/ast-bin-create-and-package.sh @@ -0,0 +1,205 @@ +#!/bin/bash +set -euo pipefail + +# AST1060 Binary Creation and Packaging Script +# Converts ELF firmware to UART bootable binary format + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "OPTIONS:" + echo " -t, --target TARGET Target triple (default: thumbv7em-none-eabihf)" + echo " -b, --binary NAME Binary name (default: aspeed-ddk)" + echo " -o, --output OUTPUT Output binary path (default: target/functional-tests.bin)" + echo " -s, --max-size SIZE Maximum binary size in bytes (default: 1048576)" + echo " -h, --help Show this help message" + echo "" + echo "EXAMPLES:" + echo " $0 # Use defaults" + echo " $0 -b my-firmware -o my-firmware.bin # Custom binary name and output" + echo " $0 -s 2097152 # Allow 2MB max size" +} + +# Default values +TARGET="thumbv7em-none-eabihf" +BINARY_NAME="aspeed-ddk" +OUTPUT_PATH="target/functional-tests.bin" +MAX_SIZE=1048576 # 1MB default + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -t|--target) + TARGET="$2" + shift 2 + ;; + -b|--binary) + BINARY_NAME="$2" + shift 2 + ;; + -o|--output) + OUTPUT_PATH="$2" + shift 2 + ;; + -s|--max-size) + MAX_SIZE="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Error: Unknown option $1" + usage + exit 1 + ;; + esac +done + +# Validate inputs +if [[ ! "$MAX_SIZE" =~ ^[0-9]+$ ]]; then + echo "Error: max-size must be a positive integer" + exit 1 +fi + +# Change to project root +cd "$PROJECT_ROOT" + +# Define paths +ELF_PATH="target/$TARGET/release/$BINARY_NAME" +RAW_BINARY_PATH="target/functional-tests-raw.bin" + +echo "AST1060 Binary Generation" +echo "=========================" +echo "Target: $TARGET" +echo "Binary: $BINARY_NAME" +echo "Output: $OUTPUT_PATH" +echo "Max size: $MAX_SIZE bytes" +echo "" + +# Check if ELF file exists +if [[ ! -f "$ELF_PATH" ]]; then + echo "Error: ELF file not found at $ELF_PATH" + echo "Please build the firmware first with:" + echo " cargo build --release --target $TARGET" + exit 1 +fi + +# Check for required tools +if ! command -v arm-none-eabi-objcopy >/dev/null 2>&1; then + echo "Error: arm-none-eabi-objcopy not found" + echo "Please install gcc-arm-none-eabi package" + exit 1 +fi + +echo "Converting ELF to raw binary..." + +# Convert ELF to raw binary +if ! arm-none-eabi-objcopy \ + -O binary \ + "$ELF_PATH" \ + "$RAW_BINARY_PATH"; then + echo "Error: Failed to convert ELF to binary" + exit 1 +fi + +# Verify raw binary was created +if [[ ! -f "$RAW_BINARY_PATH" ]]; then + echo "Error: Raw binary was not created" + exit 1 +fi + +# Get raw binary size +RAW_SIZE=$(stat -c%s "$RAW_BINARY_PATH") +echo "Raw binary size: $RAW_SIZE bytes" + +# Check size limits +if [[ $RAW_SIZE -gt $MAX_SIZE ]]; then + echo "Error: Binary too large ($RAW_SIZE bytes > $MAX_SIZE bytes limit)" + echo "Consider:" + echo " - Enabling more aggressive optimizations" + echo " - Reducing feature set" + echo " - Using release profile with size optimization" + exit 1 +fi + +echo "Wrapping with UART boot header..." + +# Check if UART boot image generator exists +UART_BOOT_SCRIPT="$SCRIPT_DIR/gen_uart_booting_image.sh" +if [[ ! -f "$UART_BOOT_SCRIPT" ]]; then + echo "Error: UART boot image generator not found at $UART_BOOT_SCRIPT" + echo "Creating simple 4-byte size prefix wrapper..." + + # Create simple wrapper if script doesn't exist + # Write 4-byte little-endian size prefix followed by binary data + python3 -c " +import struct +import sys + +raw_path = '$RAW_BINARY_PATH' +output_path = '$OUTPUT_PATH' +max_size = $MAX_SIZE + +try: + with open(raw_path, 'rb') as f: + data = f.read() + + size = len(data) + if size > max_size: + print(f'Error: Binary size {size} exceeds limit {max_size}') + sys.exit(1) + + with open(output_path, 'wb') as f: + # Write 4-byte little-endian size header + f.write(struct.pack('90%)" +fi + +echo "" +echo "Ready for UART upload to AST1060!" diff --git a/scripts/ast-uart-fw.sh b/scripts/ast-uart-fw.sh new file mode 100755 index 0000000..34502ef --- /dev/null +++ b/scripts/ast-uart-fw.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +DEST=$1 +FIRMWARE=$2 +SRST=23 +FWSPICK=18 + +echo "PULL LOW ON SRST" +pinctrl set ${SRST} op dl +echo "PULL HIGH ON FWSPICK" +pinctrl set ${FWSPICK} op pn dh +sleep 1 +echo "PULL HIGH ON SRST" +pinctrl set ${SRST} op dh +sleep 1 + +echo "Sending firmware ${FIRMWARE} to ${DEST}" +pv ${FIRMWARE} | socat -u STDIN ${DEST} From 8152e1e0f147d6002c8909e4a1901d0ffc6beca1 Mon Sep 17 00:00:00 2001 From: Will Marone Date: Fri, 21 Nov 2025 10:14:28 -0800 Subject: [PATCH 3/9] Added -q argument to silence output. --- scripts/ast-bin-create-and-package.sh | 66 +++++++++++++++++---------- 1 file changed, 41 insertions(+), 25 deletions(-) mode change 100644 => 100755 scripts/ast-bin-create-and-package.sh diff --git a/scripts/ast-bin-create-and-package.sh b/scripts/ast-bin-create-and-package.sh old mode 100644 new mode 100755 index de6c3b4..a8bf6a9 --- a/scripts/ast-bin-create-and-package.sh +++ b/scripts/ast-bin-create-and-package.sh @@ -15,12 +15,14 @@ usage() { echo " -b, --binary NAME Binary name (default: aspeed-ddk)" echo " -o, --output OUTPUT Output binary path (default: target/functional-tests.bin)" echo " -s, --max-size SIZE Maximum binary size in bytes (default: 1048576)" + echo " -q, --quiet Suppress informational output" echo " -h, --help Show this help message" echo "" echo "EXAMPLES:" echo " $0 # Use defaults" echo " $0 -b my-firmware -o my-firmware.bin # Custom binary name and output" echo " $0 -s 2097152 # Allow 2MB max size" + echo " $0 -q # Run silently" } # Default values @@ -28,6 +30,14 @@ TARGET="thumbv7em-none-eabihf" BINARY_NAME="aspeed-ddk" OUTPUT_PATH="target/functional-tests.bin" MAX_SIZE=1048576 # 1MB default +QUIET=false + +# Helper function for conditional echo +log() { + if [[ "$QUIET" != "true" ]]; then + echo "$@" + fi +} # Parse command line arguments while [[ $# -gt 0 ]]; do @@ -48,6 +58,10 @@ while [[ $# -gt 0 ]]; do MAX_SIZE="$2" shift 2 ;; + -q|--quiet) + QUIET=true + shift + ;; -h|--help) usage exit 0 @@ -73,13 +87,13 @@ cd "$PROJECT_ROOT" ELF_PATH="target/$TARGET/release/$BINARY_NAME" RAW_BINARY_PATH="target/functional-tests-raw.bin" -echo "AST1060 Binary Generation" -echo "=========================" -echo "Target: $TARGET" -echo "Binary: $BINARY_NAME" -echo "Output: $OUTPUT_PATH" -echo "Max size: $MAX_SIZE bytes" -echo "" +log "AST1060 Binary Generation" +log "=========================" +log "Target: $TARGET" +log "Binary: $BINARY_NAME" +log "Output: $OUTPUT_PATH" +log "Max size: $MAX_SIZE bytes" +log "" # Check if ELF file exists if [[ ! -f "$ELF_PATH" ]]; then @@ -96,7 +110,7 @@ if ! command -v arm-none-eabi-objcopy >/dev/null 2>&1; then exit 1 fi -echo "Converting ELF to raw binary..." +log "Converting ELF to raw binary..." # Convert ELF to raw binary if ! arm-none-eabi-objcopy \ @@ -115,7 +129,7 @@ fi # Get raw binary size RAW_SIZE=$(stat -c%s "$RAW_BINARY_PATH") -echo "Raw binary size: $RAW_SIZE bytes" +log "Raw binary size: $RAW_SIZE bytes" # Check size limits if [[ $RAW_SIZE -gt $MAX_SIZE ]]; then @@ -127,13 +141,13 @@ if [[ $RAW_SIZE -gt $MAX_SIZE ]]; then exit 1 fi -echo "Wrapping with UART boot header..." +log "Wrapping with UART boot header..." # Check if UART boot image generator exists UART_BOOT_SCRIPT="$SCRIPT_DIR/gen_uart_booting_image.sh" if [[ ! -f "$UART_BOOT_SCRIPT" ]]; then echo "Error: UART boot image generator not found at $UART_BOOT_SCRIPT" - echo "Creating simple 4-byte size prefix wrapper..." + log "Creating simple 4-byte size prefix wrapper..." # Create simple wrapper if script doesn't exist # Write 4-byte little-endian size prefix followed by binary data @@ -144,6 +158,7 @@ import sys raw_path = '$RAW_BINARY_PATH' output_path = '$OUTPUT_PATH' max_size = $MAX_SIZE +quiet = '$QUIET' == 'true' try: with open(raw_path, 'rb') as f: @@ -160,10 +175,11 @@ try: # Write binary data f.write(data) - print(f'Created UART boot image: {output_path}') - print(f'Header size: 4 bytes') - print(f'Payload size: {size} bytes') - print(f'Total size: {size + 4} bytes') + if not quiet: + print(f'Created UART boot image: {output_path}') + print(f'Header size: 4 bytes') + print(f'Payload size: {size} bytes') + print(f'Total size: {size + 4} bytes') except Exception as e: print(f'Error creating UART boot image: {e}') @@ -186,20 +202,20 @@ fi # Get final sizes UART_SIZE=$(stat -c%s "$OUTPUT_PATH") -echo "" -echo "Binary generation completed successfully!" -echo "========================================" -echo "Raw binary: $RAW_BINARY_PATH ($RAW_SIZE bytes)" -echo "UART image: $OUTPUT_PATH ($UART_SIZE bytes)" -echo "Header overhead: $((UART_SIZE - RAW_SIZE)) bytes" +log "" +log "Binary generation completed successfully!" +log "========================================" +log "Raw binary: $RAW_BINARY_PATH ($RAW_SIZE bytes)" +log "UART image: $OUTPUT_PATH ($UART_SIZE bytes)" +log "Header overhead: $((UART_SIZE - RAW_SIZE)) bytes" # Calculate utilization UTILIZATION=$((RAW_SIZE * 100 / MAX_SIZE)) -echo "Flash utilization: $UTILIZATION% of ${MAX_SIZE} bytes" +log "Flash utilization: $UTILIZATION% of ${MAX_SIZE} bytes" if [[ $UTILIZATION -gt 90 ]]; then - echo "Warning: Flash utilization is high (>90%)" + log "Warning: Flash utilization is high (>90%)" fi -echo "" -echo "Ready for UART upload to AST1060!" +log "" +log "Ready for UART upload to AST1060!" From 2247059e1b798ff115dd732e08d1c4e55fe188e1 Mon Sep 17 00:00:00 2001 From: Will Marone Date: Fri, 21 Nov 2025 16:18:32 -0800 Subject: [PATCH 4/9] Test automation expansion This fully encapsulates the test flow into Python. Use of tio has been removed and replaced entirely by pyserial, removing the complication of an external tool. TODO: The test firmware itself needs a clear delineator when it is complete that we can look for. As it stands, tests just sort of run and complete with inconsistent syntax, and eventually we run out of tests and get stuck looking at timer ISR test leftovers. As it stands the script will time out after 600 seconds. All test output is logged to a file within the working directory. The script also serves as a tool to manipulate the GPIOs controlling boot functionality, and can be configured to target different GPIOs if needed. --- scripts/ast-uart-fw.sh | 18 -- scripts/uart-test-exec.py | 474 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 474 insertions(+), 18 deletions(-) delete mode 100755 scripts/ast-uart-fw.sh create mode 100644 scripts/uart-test-exec.py diff --git a/scripts/ast-uart-fw.sh b/scripts/ast-uart-fw.sh deleted file mode 100755 index 34502ef..0000000 --- a/scripts/ast-uart-fw.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -DEST=$1 -FIRMWARE=$2 -SRST=23 -FWSPICK=18 - -echo "PULL LOW ON SRST" -pinctrl set ${SRST} op dl -echo "PULL HIGH ON FWSPICK" -pinctrl set ${FWSPICK} op pn dh -sleep 1 -echo "PULL HIGH ON SRST" -pinctrl set ${SRST} op dh -sleep 1 - -echo "Sending firmware ${FIRMWARE} to ${DEST}" -pv ${FIRMWARE} | socat -u STDIN ${DEST} diff --git a/scripts/uart-test-exec.py b/scripts/uart-test-exec.py new file mode 100644 index 0000000..f5fbc1f --- /dev/null +++ b/scripts/uart-test-exec.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python3 +""" +AST1060 UART Test Execution Script + +Provides GPIO control for SRST/FWSPICK pins and automated firmware upload +with test execution monitoring via pyserial. +""" + +import argparse +import subprocess +import time +import os +import sys +import threading +from pathlib import Path +from typing import Optional, Tuple + +try: + import serial +except ImportError: + print("Error: pyserial not installed. Install with: pip install pyserial") + sys.exit(1) + + +class UartTestExecutor: + """Handles AST1060 UART test execution with GPIO control.""" + + def __init__(self, args): + self.args = args + self.serial_port: Optional[serial.Serial] = None + self.log_file = args.log_file or f"uart-test-{os.getpid()}.log" + self.log_file_handle = None + + def log(self, message: str): + """Print message unless in quiet mode.""" + if not self.args.quiet: + print(message) + + def run_command(self, cmd: list, check: bool = True) -> Tuple[int, str, str]: + """Run command and return (returncode, stdout, stderr).""" + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=False + ) + if check and result.returncode != 0: + raise subprocess.CalledProcessError( + result.returncode, cmd, result.stdout, result.stderr + ) + return result.returncode, result.stdout, result.stderr + except FileNotFoundError: + raise RuntimeError(f"Command not found: {cmd[0]}") + + def gpio_set(self, pin: int, state: str): + """Set GPIO pin state using pinctrl.""" + cmd = ["pinctrl", "set", str(pin), "op", state] + self.log(f"GPIO {pin}: {state}") + + if self.args.dry_run: + self.log(f"DRY RUN: {' '.join(cmd)}") + return + + try: + self.run_command(cmd) + except Exception as e: + raise RuntimeError(f"Failed to set GPIO {pin} to {state}: {e}") + + def toggle_srst(self, state: str): + """Toggle SRST pin (dl=low, dh=high).""" + self.gpio_set(self.args.srst_pin, state) + + def toggle_fwspick(self, state: str): + """Toggle FWSPICK pin (dh=high, dl=low).""" + # Note: FWSPICK uses 'pn dh' for high state as in original script + if state == "dh": + cmd_state = "pn dh" + else: + cmd_state = state + + cmd = ["pinctrl", "set", str(self.args.fwspick_pin), "op"] + cmd_state.split() + self.log(f"GPIO {self.args.fwspick_pin} (FWSPICK): {state}") + + if self.args.dry_run: + self.log(f"DRY RUN: {' '.join(cmd)}") + return + + try: + self.run_command(cmd) + except Exception as e: + raise RuntimeError(f"Failed to set FWSPICK {self.args.fwspick_pin} to {state}: {e}") + + def sequence_to_fwspick_mode(self): + """Execute sequence to enter FWSPICK mode.""" + self.log("Entering FWSPICK mode sequence...") + self.toggle_srst("dl") # SRST low + time.sleep(0.1) + self.toggle_fwspick("dh") # FWSPICK high + time.sleep(1) + self.toggle_srst("dh") # SRST high + time.sleep(1) + self.log("FWSPICK mode sequence complete") + + def sequence_to_normal_mode(self): + """Execute sequence to enter normal boot mode.""" + self.log("Entering normal boot mode sequence...") + self.toggle_fwspick("dl") # FWSPICK low + time.sleep(0.1) + self.toggle_srst("dl") # SRST low + time.sleep(0.5) + self.toggle_srst("dh") # SRST high + time.sleep(2) + self.log("Normal boot mode sequence complete") + + def open_serial(self) -> bool: + """Open serial port connection.""" + if self.args.skip_uart: + self.log("Skipping UART setup") + return True + + if self.args.dry_run: + self.log("DRY RUN: Would open serial port") + return True + + try: + self.serial_port = serial.Serial( + port=self.args.uart_device, + baudrate=self.args.baudrate, + timeout=1.0, + write_timeout=1.0 + ) + + # Open log file + self.log_file_handle = open(self.log_file, 'w') + + self.log(f"Serial port opened: {self.args.uart_device} @ {self.args.baudrate} baud") + self.log(f"Logging to: {self.log_file}") + return True + + except Exception as e: + raise RuntimeError(f"Failed to open serial port: {e}") + + def close_serial(self): + """Close serial port connection.""" + if self.serial_port: + self.serial_port.close() + self.serial_port = None + + if self.log_file_handle: + self.log_file_handle.close() + self.log_file_handle = None + + def read_serial_data(self, timeout_seconds: float = 1.0) -> str: + """Read available data from serial port.""" + if not self.serial_port or self.args.skip_uart: + return "" + + if self.args.dry_run: + return "" + + try: + # Set a short timeout for non-blocking read + self.serial_port.timeout = timeout_seconds + data = self.serial_port.read(1024) + + if data: + decoded = data.decode('utf-8', errors='ignore') + + # Log to file + if self.log_file_handle: + self.log_file_handle.write(decoded) + self.log_file_handle.flush() + + return decoded + + return "" + + except Exception as e: + self.log(f"Serial read error: {e}") + return "" + + def write_serial_data(self, data: bytes) -> bool: + """Write data to serial port.""" + if not self.serial_port or self.args.skip_uart: + return True + + if self.args.dry_run: + self.log(f"DRY RUN: Would write {len(data)} bytes to serial") + return True + + try: + bytes_written = self.serial_port.write(data) + self.serial_port.flush() + return bytes_written == len(data) + + except Exception as e: + self.log(f"Serial write error: {e}") + return False + + def wait_for_uart_ready(self, timeout: int = 30) -> bool: + """Wait for 'U' character indicating UART bootloader ready.""" + if self.args.skip_uart: + self.log("Skipping UART ready check") + return True + + self.log(f"Waiting for UART ready signal ('U') with {timeout}s timeout...") + + if self.args.dry_run: + self.log("DRY RUN: Would wait for UART ready") + return True + + start_time = time.time() + buffer = "" + + while time.time() - start_time < timeout: + data = self.read_serial_data(0.1) + if data: + buffer += data + if not self.args.quiet: + print(data, end='', flush=True) + + # Look for 'U' character + if 'U' in buffer: + self.log("\nUART bootloader ready detected!") + return True + + self.log("\nTimeout waiting for UART ready signal") + return False + + def upload_firmware(self) -> bool: + """Upload firmware via serial port.""" + if self.args.skip_uart or not self.args.firmware: + self.log("Skipping firmware upload") + return True + + firmware_path = Path(self.args.firmware) + if not firmware_path.exists(): + raise RuntimeError(f"Firmware file not found: {firmware_path}") + + self.log(f"Uploading firmware: {firmware_path}") + + if self.args.dry_run: + self.log("DRY RUN: Would upload firmware") + return True + + try: + with open(firmware_path, 'rb') as f: + firmware_data = f.read() + + self.log(f"Uploading {len(firmware_data)} bytes...") + + # Upload firmware in chunks to avoid overwhelming the bootloader + chunk_size = 1024 + bytes_sent = 0 + + for i in range(0, len(firmware_data), chunk_size): + chunk = firmware_data[i:i + chunk_size] + + if not self.write_serial_data(chunk): + self.log("Failed to write firmware chunk") + return False + + bytes_sent += len(chunk) + + # Small delay between chunks + time.sleep(0.01) + + # Progress indicator + if not self.args.quiet and bytes_sent % (chunk_size * 10) == 0: + progress = (bytes_sent * 100) // len(firmware_data) + print(f"\rProgress: {progress}%", end='', flush=True) + + if not self.args.quiet: + print() # New line after progress + + self.log("Firmware upload completed") + return True + + except Exception as e: + self.log(f"Failed to upload firmware: {e}") + return False + + def monitor_test_execution(self, timeout: int = 600) -> bool: + """Monitor test execution via serial port.""" + if self.args.skip_uart: + self.log("Skipping test monitoring") + return True + + # Use command line timeout if provided, otherwise use parameter default + actual_timeout = getattr(self.args, 'test_timeout', timeout) + self.log(f"Monitoring test execution with {actual_timeout}s timeout...") + + if self.args.dry_run: + self.log("DRY RUN: Would monitor test execution") + return True + + start_time = time.time() + buffer = "" + test_results = {"passed": 0, "failed": 0, "skipped": 0} + + while time.time() - start_time < actual_timeout: + data = self.read_serial_data(0.5) + if data: + buffer += data + if not self.args.quiet: + print(data, end='', flush=True) + + # Parse test results + lines = buffer.split('\n') + for line in lines: + if 'PASS' in line: + test_results['passed'] += 1 + elif 'FAIL' in line: + test_results['failed'] += 1 + elif 'SKIP' in line: + test_results['skipped'] += 1 + + # Check for completion or failure + if 'Test Suite' in line and 'Complete' in line: + self.log(f"\nTest execution completed!") + self.log(f"Results: {test_results}") + return test_results['failed'] == 0 + + if 'panic' in line.lower(): + self.log(f"\nPanic detected in test execution!") + return False + + # Keep only last few lines in buffer + buffer = '\n'.join(lines[-10:]) + + self.log(f"\nTest monitoring timeout. Results so far: {test_results}") + return test_results['failed'] == 0 + + def cleanup(self): + """Clean up resources.""" + self.close_serial() + + def run_full_test_sequence(self) -> bool: + """Execute the complete test sequence.""" + try: + # Open serial port + if not self.open_serial(): + return False + + # Enter FWSPICK mode + self.sequence_to_fwspick_mode() + + # Wait for UART ready + if not self.wait_for_uart_ready(): + return False + + # Upload firmware + if not self.upload_firmware(): + return False + + # Monitor test execution (starts immediately after upload) + return self.monitor_test_execution() + + finally: + self.cleanup() + + +def main(): + parser = argparse.ArgumentParser( + description="AST1060 UART Test Execution Script", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Full test sequence + ./uart-test-exec.py /dev/ttyUSB0 firmware.bin + + # Manual GPIO control + ./uart-test-exec.py --manual-srst low + ./uart-test-exec.py --manual-fwspick high + + # Sequence control + ./uart-test-exec.py --sequence fwspick-mode + ./uart-test-exec.py --sequence normal-mode + + # Custom pin numbers and timeout + ./uart-test-exec.py --srst-pin 25 --fwspick-pin 20 --test-timeout 300 /dev/ttyUSB0 firmware.bin + """ + ) + + # Positional arguments + parser.add_argument("uart_device", nargs='?', + help="UART device path (e.g., /dev/ttyUSB0)") + parser.add_argument("firmware", nargs='?', + help="Firmware binary file path") + + # GPIO control + parser.add_argument("--srst-pin", type=int, default=23, + help="SRST GPIO pin number (default: 23)") + parser.add_argument("--fwspick-pin", type=int, default=18, + help="FWSPICK GPIO pin number (default: 18)") + + # Manual GPIO operations + parser.add_argument("--manual-srst", choices=['low', 'high', 'dl', 'dh'], + help="Manually toggle SRST pin") + parser.add_argument("--manual-fwspick", choices=['low', 'high', 'dl', 'dh'], + help="Manually toggle FWSPICK pin") + + # Sequence operations + parser.add_argument("--sequence", choices=['fwspick-mode', 'normal-mode'], + help="Run GPIO sequence") + + # UART settings + parser.add_argument("-b", "--baudrate", type=int, default=115200, + help="UART baud rate (default: 115200)") + parser.add_argument("--test-timeout", type=int, default=600, + help="Test execution monitoring timeout in seconds (default: 600)") + parser.add_argument("--log-file", + help="Log file path (auto-generated if not specified)") + + # Control flags + parser.add_argument("--skip-uart", action="store_true", + help="Skip all UART operations") + parser.add_argument("-q", "--quiet", action="store_true", + help="Run silently (no output)") + parser.add_argument("--dry-run", action="store_true", + help="Show what would be done without executing") + + args = parser.parse_args() + + # Validate arguments + if not any([args.manual_srst, args.manual_fwspick, args.sequence, args.uart_device]): + parser.error("Must specify either manual GPIO control, sequence, or UART device") + + if args.uart_device and not args.skip_uart and not Path(args.uart_device).exists(): + parser.error(f"UART device not found: {args.uart_device}") + + executor = UartTestExecutor(args) + + try: + # Handle manual GPIO operations + if args.manual_srst: + state = "dl" if args.manual_srst in ['low', 'dl'] else "dh" + executor.toggle_srst(state) + return 0 + + if args.manual_fwspick: + state = "dh" if args.manual_fwspick in ['high', 'dh'] else "dl" + executor.toggle_fwspick(state) + return 0 + + # Handle sequence operations + if args.sequence == 'fwspick-mode': + executor.sequence_to_fwspick_mode() + return 0 + elif args.sequence == 'normal-mode': + executor.sequence_to_normal_mode() + return 0 + + # Run full test sequence + if executor.run_full_test_sequence(): + executor.log("✅ Test execution completed successfully!") + return 0 + else: + executor.log("❌ Test execution failed!") + return 1 + + except KeyboardInterrupt: + executor.log("\nInterrupted by user") + return 130 + except Exception as e: + executor.log(f"Error: {e}") + return 1 + finally: + executor.cleanup() + + +if __name__ == "__main__": + sys.exit(main()) From 7f11e85a3a7ba0559ddb6b228d8413629dcefafd Mon Sep 17 00:00:00 2001 From: Will Marone Date: Fri, 21 Nov 2025 20:10:22 -0800 Subject: [PATCH 5/9] Update README.md --- README.md | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index d8e23a7..b622487 100644 --- a/README.md +++ b/README.md @@ -81,32 +81,17 @@ $ cargo build --release The recommended host platform is a Raspberry Pi, per ASpeed. Connecting two GPIO from the Pi to SRST pin 1 and FWSPICK pin 2 will allow the upload script to manage UART boot state and device ready. Check the upload script for the correct pins. -### TIO +### Test Execution -TIO is used as a multiplexer to allow observing returned data streams while other applications write. +Using the UART boot image created above, the uart-test-exec.py script will load the binary on the device and monitor all output. -1. Clone https://github.com/tio/tio.git and follow its build instructions. Ensure HEAD is 3af4c559 or newer. +$ python3 ./scripts/uart-test-exec.py uart_ast10x0.bin -### Run - -1. Run TIO: - ``` - ./build/src/tio -S unix:/tmp/tio-socket-ast1060 - ``` - -2. Run the upload script - ``` - aspeed-rust $ ./scripts/ast-uart-fw.sh UNIX-CONNECT:/tmp/tio-socket-ast1060 uart_ast10x0.bin - ``` - The upload may take a while due to the size of the binary. On the TIO terminal you should see: +The script will toggle the GPIOs in the necessary order to reset the device into UART boot mode, then once "U" is observed from the device, it will upload the binary and begin observing the output. All test output will be printed to the screen and logged for later use. - ``` - UP0c0 - Hello, world!! +Running +$ pythone3 ./scripts/uart-test-exec.py --help - aspeed_ddk::hash::Sha256... - ``` - - As the test begins executing. +will provide details on multiple additional options including manual toggling of GPIOs, altering the GPIO number, and other configuration options (UART baud rate, timeouts, quiet operation, etc.) From 14ad5890ca13a9a32b0640c618d956dc94fe0ba2 Mon Sep 17 00:00:00 2001 From: Will Marone Date: Mon, 24 Nov 2025 11:54:39 -0800 Subject: [PATCH 6/9] Updated tests for output Made tests outputs more consistent, but further refinements are necessary. Also added a test end print and adjusted the script to key off of it. --- scripts/uart-test-exec.py | 2 +- src/main.rs | 23 +++++++++++---------- src/spi/spitest.rs | 18 ++++++++--------- src/tests/functional/ecdsa_test.rs | 6 +++--- src/tests/functional/gpio_test.rs | 16 +++++++-------- src/tests/functional/hash_test.rs | 4 ++-- src/tests/functional/hmac_test.rs | 4 ++-- src/tests/functional/i2c_test.rs | 32 +++++++++++++++--------------- src/tests/functional/rsa_test.rs | 8 ++++---- 9 files changed, 58 insertions(+), 55 deletions(-) diff --git a/scripts/uart-test-exec.py b/scripts/uart-test-exec.py index f5fbc1f..1002ea1 100644 --- a/scripts/uart-test-exec.py +++ b/scripts/uart-test-exec.py @@ -317,7 +317,7 @@ def monitor_test_execution(self, timeout: int = 600) -> bool: test_results['skipped'] += 1 # Check for completion or failure - if 'Test Suite' in line and 'Complete' in line: + if 'COMPLETE' in line: self.log(f"\nTest execution completed!") self.log(f"Results: {test_results}") return test_results['failed'] == 0 diff --git a/src/main.rs b/src/main.rs index e352595..13c7026 100644 --- a/src/main.rs +++ b/src/main.rs @@ -177,10 +177,10 @@ fn validate_digest( } if matches && byte_index == expected.len() { - writeln!(uart, "{algorithm} test vector validation: PASSED ✅").unwrap(); + writeln!(uart, "{algorithm} test vector validation: PASS").unwrap(); true } else { - writeln!(uart, "{algorithm} test vector validation: FAILED ❌").unwrap(); + writeln!(uart, "{algorithm} test vector validation: FAIL").unwrap(); write!(uart, "Expected: ").unwrap(); for &byte in expected { write!(uart, "{byte:02x}").unwrap(); @@ -226,9 +226,9 @@ fn test_owned_sha256(uart: &mut UartController<'_>, hace: ast1060_pac::Hace) { ]; if validate_digest(&digest.value, &expected_sha256, "SHA256", uart) { - writeln!(uart, "SHA256 owned API: PASSED ✅").unwrap(); + writeln!(uart, "SHA256 owned API: PASS").unwrap(); } else { - writeln!(uart, "SHA256 owned API: FAILED ❌").unwrap(); + writeln!(uart, "SHA256 owned API: FAIL").unwrap(); } } @@ -254,9 +254,9 @@ fn test_owned_sha384(uart: &mut UartController<'_>, hace: ast1060_pac::Hace) { ]; if validate_digest(&digest.value, &expected_sha384, "SHA384", uart) { - writeln!(uart, "SHA384 owned API: PASSED ✅").unwrap(); + writeln!(uart, "SHA384 owned API: PASS").unwrap(); } else { - writeln!(uart, "SHA384 owned API: FAILED ❌").unwrap(); + writeln!(uart, "SHA384 owned API: FAIL").unwrap(); } // Demonstrate controller recovery by using it again @@ -264,7 +264,7 @@ fn test_owned_sha384(uart: &mut UartController<'_>, hace: ast1060_pac::Hace) { let context2 = context2.update(b"Reused controller").unwrap(); let (_digest2, _final_controller) = context2.finalize().unwrap(); - writeln!(uart, "Controller recovery: PASSED ✅").unwrap(); + writeln!(uart, "Controller recovery: PASS").unwrap(); } /// Test owned SHA512 API demonstrating cancellation @@ -278,7 +278,7 @@ fn test_owned_sha512(uart: &mut UartController<'_>, hace: ast1060_pac::Hace) { // Demonstrate cancellation - returns controller without computing digest let recovered_controller = context.cancel(); - writeln!(uart, "SHA512 cancellation: PASSED ✅").unwrap(); + writeln!(uart, "SHA512 cancellation: PASS").unwrap(); // Use recovered controller for actual computation with known test vector // Test with known test vector: "abc" -> SHA512 @@ -300,9 +300,9 @@ fn test_owned_sha512(uart: &mut UartController<'_>, hace: ast1060_pac::Hace) { ]; if validate_digest(&digest.value, &expected_sha512, "SHA512", uart) { - writeln!(uart, "SHA512 owned API: PASSED ✅").unwrap(); + writeln!(uart, "SHA512 owned API: PASS").unwrap(); } else { - writeln!(uart, "SHA512 owned API: FAILED ❌").unwrap(); + writeln!(uart, "SHA512 owned API: FAIL").unwrap(); } } @@ -372,6 +372,9 @@ fn main() -> ! { gpio_test::test_gpio_flash_power(&mut uart_controller); spi::spitest::test_spi2(&mut uart_controller); } + + writeln!(uart_controller, "\r\nCOMPLETE\r\n").unwrap(); + // Initialize the peripherals here if needed loop { cortex_m::asm::wfi(); diff --git a/src/spi/spitest.rs b/src/spi/spitest.rs index 7c94b72..cace60e 100644 --- a/src/spi/spitest.rs +++ b/src/spi/spitest.rs @@ -187,7 +187,7 @@ pub fn test_read_jedec, E>(uart: &mut UartController< astdebug::print_array_u8(uart, &id); } _ => { - test_log!(uart, "Error:: Failed to read JEDEC ID"); + test_log!(uart, "Error:: Failed to read JEDEC ID (FAIL)"); } } } @@ -282,9 +282,9 @@ pub fn test_cs, E>( == core::slice::from_raw_parts(ptr_read, len); } if result { - test_log!(uart, "read write test passed!"); + test_log!(uart, "read write test PASS!"); } else { - test_log!(uart, "ERROR:: read write test failed!!"); + test_log!(uart, "ERROR:: read write test FAIL!!"); test_log!(uart, "write buffer:"); astdebug::print_array_u8(uart, wbuf); test_log!(uart, "read buffer:"); @@ -448,10 +448,10 @@ pub fn test_spi(uart: &mut UartController<'_>) { match flash_device.nor_read_jedec_id() { Ok(id) => match NorFlashBlockDevice::from_jedec_id(flash_device, id) { Ok(mut blockdev) => test_block_device::<_>(&mut blockdev), - Err(_e) => test_log!(uart, "start block device using jedec id failed"), + Err(_e) => test_log!(uart, "start block device using jedec id failed (FAIL)"), }, _ => { - test_log!(uart, "Error:: Failed to read JEDEC ID"); + test_log!(uart, "Error:: Failed to read JEDEC ID (FAIL)"); } } } else { @@ -591,8 +591,8 @@ pub fn test_block_device(blockdev: &mut NorFlashBlockDevice) testsize ); match blockdev.program(norflashblockdevice::BlockAddrUsize(addr), wbuf) { - Ok(()) => test_log!(uartc, "program successful"), - Err(_e) => test_log!(uartc, "program failed"), + Ok(()) => test_log!(uartc, "program PASS"), + Err(_e) => test_log!(uartc, "program FAIL"), } let _ = blockdev.read(norflashblockdevice::BlockAddrUsize(addr), rbuf); @@ -603,9 +603,9 @@ pub fn test_block_device(blockdev: &mut NorFlashBlockDevice) == core::slice::from_raw_parts(ptr_read, testsize); } if result { - test_log!(uartc, "read write test passed!"); + test_log!(uartc, "read write test passed (PASS)!"); } else { - test_log!(uartc, "ERROR:: read write test failed!!"); + test_log!(uartc, "ERROR:: read write test failed (FAIL)!!"); test_log!(uartc, "write buffer:"); astdebug::print_array_u8(&mut uartc, wbuf); test_log!(uartc, "read buffer:"); diff --git a/src/tests/functional/ecdsa_test.rs b/src/tests/functional/ecdsa_test.rs index 39d6601..611ccd5 100644 --- a/src/tests/functional/ecdsa_test.rs +++ b/src/tests/functional/ecdsa_test.rs @@ -107,9 +107,9 @@ pub fn run_ecdsa_tests( writeln!(uart, "\r\nTest case {i}... ").unwrap(); let _ = match (result.is_ok(), vec.result) { - (true, true) => writeln!(uart, "\rresult expected (pass), Pass"), - (false, false) => writeln!(uart, "\rresult expected (fail), Pass"), - _ => writeln!(uart, "\rresult unexpected (got {result:?}), Failed"), + (true, true) => writeln!(uart, "\rresult expected (pass), PASS"), + (false, false) => writeln!(uart, "\rresult expected (fail), PASS"), + _ => writeln!(uart, "\rresult unexpected (got {result:?}), FAIL"), }; } } diff --git a/src/tests/functional/gpio_test.rs b/src/tests/functional/gpio_test.rs index c971ba6..35c4a74 100644 --- a/src/tests/functional/gpio_test.rs +++ b/src/tests/functional/gpio_test.rs @@ -34,24 +34,24 @@ pub fn test_gpioa(uart: &mut UartController<'_>) { let mut pa3 = gpioa.pa3.into_open_drain_output::(); pa3.set_low().unwrap(); if pa3.is_set_low().unwrap() { - uart.write_all(b"\rGPIOA pin3 set low successfully\r\n") + uart.write_all(b"\rGPIOA pin3 set low successfully (PASS)\r\n") .unwrap(); } pa3.set_high().unwrap(); if pa3.is_set_high().unwrap() { - uart.write_all(b"\rGPIOA pin3 set high successfully\r\n") + uart.write_all(b"\rGPIOA pin3 set high successfully (PASS)\r\n") .unwrap(); } let mut pa4 = gpioa.pa4.into_push_pull_output(); pa4.set_low().unwrap(); if pa4.is_set_low().unwrap() { - uart.write_all(b"\rGPIOA pin4 set low successfully\r\n") + uart.write_all(b"\rGPIOA pin4 set low successfully (PASS)\r\n") .unwrap(); } pa4.set_high().unwrap(); if pa4.is_set_high().unwrap() { - uart.write_all(b"\rGPIOA pin4 set high successfully\r\n") + uart.write_all(b"\rGPIOA pin4 set high successfully (PASS)\r\n") .unwrap(); } } @@ -90,12 +90,12 @@ pub fn test_gpio_bmc_reset(uart: &mut UartController<'_>) { let mut pm5 = gpiom.pm5.into_push_pull_output(); pm5.set_low().unwrap(); if pm5.is_set_low().unwrap() { - uart.write_all(b"\r\nGPIOM pin5 set low successfully\r\n") + uart.write_all(b"\r\nGPIOM pin5 set low successfully (PASS)\r\n") .unwrap(); } pm5.set_high().unwrap(); if pm5.is_set_high().unwrap() { - uart.write_all(b"\r\nGPIOM pin5 set high successfully\r\n") + uart.write_all(b"\r\nGPIOM pin5 set high successfully (PASS)\r\n") .unwrap(); } } @@ -111,12 +111,12 @@ pub fn test_gpio_bmc_reset(uart: &mut UartController<'_>) { let mut ph2 = gpioh.ph2.into_push_pull_output(); ph2.set_low().unwrap(); if ph2.is_set_low().unwrap() { - uart.write_all(b"\r\nGPIOH pin2 set low successfully\r\n") + uart.write_all(b"\r\nGPIOH pin2 set low successfully (PASS)\r\n") .unwrap(); } ph2.set_high().unwrap(); if ph2.is_set_high().unwrap() { - uart.write_all(b"\r\nGPIOH pin2 set high successfully\r\n") + uart.write_all(b"\r\nGPIOH pin2 set high successfully (PASS)\r\n") .unwrap(); } } diff --git a/src/tests/functional/hash_test.rs b/src/tests/functional/hash_test.rs index bec1fef..604ee92 100644 --- a/src/tests/functional/hash_test.rs +++ b/src/tests/functional/hash_test.rs @@ -93,9 +93,9 @@ where if let Some(expected) = expected { if output.as_ref() == expected { - writeln!(uart, "\r\n{}: Test passed!", core::any::type_name::()).unwrap(); + writeln!(uart, "\r\n{}: Test PASS", core::any::type_name::()).unwrap(); } else { - writeln!(uart, "\r\n{}: Test failed!", core::any::type_name::()).unwrap(); + writeln!(uart, "\r\n{}: Test FAIL", core::any::type_name::()).unwrap(); writeln!(uart, "Expected:").unwrap(); print_hex_array(uart, expected, 16); writeln!(uart, "Got:").unwrap(); diff --git a/src/tests/functional/hmac_test.rs b/src/tests/functional/hmac_test.rs index 37a28b4..62ec57f 100644 --- a/src/tests/functional/hmac_test.rs +++ b/src/tests/functional/hmac_test.rs @@ -99,9 +99,9 @@ where if let Some(expected) = expected { if output.as_ref() == expected { - writeln!(uart, "\r\n{}: Test passed!", core::any::type_name::()).unwrap(); + writeln!(uart, "\r\n{}: Test PASS", core::any::type_name::()).unwrap(); } else { - writeln!(uart, "\r\n{}: Test failed!", core::any::type_name::()).unwrap(); + writeln!(uart, "\r\n{}: Test FAIL", core::any::type_name::()).unwrap(); writeln!(uart, "Expected:").unwrap(); print_hex_array(uart, expected, 16); writeln!(uart, "Got:").unwrap(); diff --git a/src/tests/functional/i2c_test.rs b/src/tests/functional/i2c_test.rs index 2f33a50..3499eb9 100644 --- a/src/tests/functional/i2c_test.rs +++ b/src/tests/functional/i2c_test.rs @@ -129,18 +129,18 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { if true { match i2c1.hardware.write(addr, &buf) { Ok(val) => { - writeln!(uart, "i2c write ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c write err: {e:?}\r").unwrap(); + writeln!(uart, "i2c write FAIL: {e:?}\r").unwrap(); } } match i2c1.hardware.read(addr, &mut buf) { Ok(val) => { - writeln!(uart, "i2c read ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c read PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c read err: {e:?}\r").unwrap(); + writeln!(uart, "i2c read FAIL: {e:?}\r").unwrap(); } } writeln!(uart, "after read data {:#x}, expected: 0x81\r\n", buf[0]).unwrap(); @@ -152,18 +152,18 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { buf[0] = off; match i2c1.hardware.write(addr, &buf) { Ok(val) => { - writeln!(uart, "i2c write ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c write err: {e:?}\r").unwrap(); + writeln!(uart, "i2c write FAIL: {e:?}\r").unwrap(); } } match i2c1.hardware.read(addr, &mut buf) { Ok(val) => { - writeln!(uart, "i2c read ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c read PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c read err: {e:?}\r").unwrap(); + writeln!(uart, "i2c read FAIL: {e:?}\r").unwrap(); } } writeln!( @@ -178,28 +178,28 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { let buf2 = [0x82, 0x3]; match i2c1.hardware.write(addr, &buf2) { Ok(val) => { - writeln!(uart, "i2c write ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c write err: {e:?}\r").unwrap(); + writeln!(uart, "i2c write FAIL: {e:?}\r").unwrap(); } } buf[0] = 0x82; writeln!(uart, "########### read 0x82 \r\n").unwrap(); match i2c1.hardware.write(addr, &buf) { Ok(val) => { - writeln!(uart, "i2c write ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c write err: {e:?}\r").unwrap(); + writeln!(uart, "i2c write FAIL: {e:?}\r").unwrap(); } } match i2c1.hardware.read(addr, &mut buf) { Ok(val) => { - writeln!(uart, "i2c read ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c read PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c read err: {e:?}\r").unwrap(); + writeln!(uart, "i2c read FAIL: {e:?}\r").unwrap(); } } writeln!( @@ -286,10 +286,10 @@ pub fn test_i2c_slave(uart: &mut UartController<'_>) { .i2c_aspeed_slave_register(TEST_TARGET.address, None) { Ok(val) => { - writeln!(uart, "i2c slave register ok: {val:?}\r").unwrap(); + writeln!(uart, "i2c slave register PASS: {val:?}\r").unwrap(); } Err(e) => { - writeln!(uart, "i2c slave register err: {e:?}\r").unwrap(); + writeln!(uart, "i2c slave register FAIL: {e:?}\r").unwrap(); } } diff --git a/src/tests/functional/rsa_test.rs b/src/tests/functional/rsa_test.rs index caace49..92cc62e 100644 --- a/src/tests/functional/rsa_test.rs +++ b/src/tests/functional/rsa_test.rs @@ -90,10 +90,10 @@ where continue; } - writeln!(uart, "\rRSA vector[{i}] sign passed").ok(); + writeln!(uart, "\rRSA vector[{i}] sign PASS").ok(); } Err(_err) => { - writeln!(uart, "\rRSA vector[{i}] sign failed").ok(); + writeln!(uart, "\rRSA vector[{i}] sign FAIL").ok(); } } } @@ -170,10 +170,10 @@ where match result { Ok(_decrypted) => { - writeln!(uart, "\rRSA vector[{i}] verify passed").ok(); + writeln!(uart, "\rRSA vector[{i}] verify PASS").ok(); } Err(err) => { - writeln!(uart, "\rRSA vector[{i}] verify failed: {err:?}").ok(); + writeln!(uart, "\rRSA vector[{i}] verify FAIL: {err:?}").ok(); } } } From ef4fd759669e74d9e3572c326905f9f56bbcc688 Mon Sep 17 00:00:00 2001 From: Will Marone Date: Tue, 2 Dec 2025 13:53:42 -0800 Subject: [PATCH 7/9] Change I2C test to use i2c2 (hw i2c3) Move i2c to i2c2 --- src/tests/functional/i2c_test.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/tests/functional/i2c_test.rs b/src/tests/functional/i2c_test.rs index 3499eb9..29ef642 100644 --- a/src/tests/functional/i2c_test.rs +++ b/src/tests/functional/i2c_test.rs @@ -112,8 +112,8 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { .smbus_alert(false) .speed(I2cSpeed::Standard) .build(); - let mut i2c1: I2cController< - Ast1060I2c, + let mut i2c2: I2cController< + Ast1060I2c, NoOpLogger, > = I2cController { hardware: Ast1060I2c::new(UartLogger::new(&mut dbg_uart)), @@ -121,13 +121,13 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { logger: NoOpLogger {}, }; - pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_I2C1); - i2c1.hardware.init(&mut i2c1.config); + pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_I2C2); + i2c2.hardware.init(&mut i2c2.config); let addr = 0x2e; //device ADT7490 let mut buf = [0x4e]; if true { - match i2c1.hardware.write(addr, &buf) { + match i2c2.hardware.write(addr, &buf) { Ok(val) => { writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } @@ -135,7 +135,7 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { writeln!(uart, "i2c write FAIL: {e:?}\r").unwrap(); } } - match i2c1.hardware.read(addr, &mut buf) { + match i2c2.hardware.read(addr, &mut buf) { Ok(val) => { writeln!(uart, "i2c read PASS: {val:?}\r").unwrap(); } @@ -150,7 +150,7 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { let mut buf = [0x0]; for (i, &off) in reg_addr.iter().enumerate() { buf[0] = off; - match i2c1.hardware.write(addr, &buf) { + match i2c2.hardware.write(addr, &buf) { Ok(val) => { writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } @@ -158,7 +158,7 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { writeln!(uart, "i2c write FAIL: {e:?}\r").unwrap(); } } - match i2c1.hardware.read(addr, &mut buf) { + match i2c2.hardware.read(addr, &mut buf) { Ok(val) => { writeln!(uart, "i2c read PASS: {val:?}\r").unwrap(); } @@ -176,7 +176,7 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { if false { writeln!(uart, "########### write 0x3 to offset 0x82 \r\n").unwrap(); let buf2 = [0x82, 0x3]; - match i2c1.hardware.write(addr, &buf2) { + match i2c2.hardware.write(addr, &buf2) { Ok(val) => { writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } @@ -186,7 +186,7 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { } buf[0] = 0x82; writeln!(uart, "########### read 0x82 \r\n").unwrap(); - match i2c1.hardware.write(addr, &buf) { + match i2c2.hardware.write(addr, &buf) { Ok(val) => { writeln!(uart, "i2c write PASS: {val:?}\r").unwrap(); } @@ -194,7 +194,7 @@ pub fn test_i2c_master(uart: &mut UartController<'_>) { writeln!(uart, "i2c write FAIL: {e:?}\r").unwrap(); } } - match i2c1.hardware.read(addr, &mut buf) { + match i2c2.hardware.read(addr, &mut buf) { Ok(val) => { writeln!(uart, "i2c read PASS: {val:?}\r").unwrap(); } From 97f0dfdd0f8020c03071390b6c2017dfa87ecf6b Mon Sep 17 00:00:00 2001 From: Will Marone Date: Fri, 5 Dec 2025 09:47:09 -0800 Subject: [PATCH 8/9] Add support for upload-only. The --upload-only parameter lets us target devices that we have only manual SRST and UART control over. --- scripts/uart-test-exec.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/uart-test-exec.py b/scripts/uart-test-exec.py index 1002ea1..7eb6950 100644 --- a/scripts/uart-test-exec.py +++ b/scripts/uart-test-exec.py @@ -380,6 +380,9 @@ def main(): # Custom pin numbers and timeout ./uart-test-exec.py --srst-pin 25 --fwspick-pin 20 --test-timeout 300 /dev/ttyUSB0 firmware.bin + + # Upload-only (no GPIO, no monitoring) + ./uart-test-exec.py --upload-only /dev/ttyUSB0 firmware.bin """ ) @@ -420,13 +423,21 @@ def main(): help="Run silently (no output)") parser.add_argument("--dry-run", action="store_true", help="Show what would be done without executing") - + # NEW: upload-only mode (no GPIO, no monitoring) + parser.add_argument("--upload-only", action="store_true", + help="Skip all GPIO commands, open UART, upload firmware, then exit") + args = parser.parse_args() # Validate arguments - if not any([args.manual_srst, args.manual_fwspick, args.sequence, args.uart_device]): - parser.error("Must specify either manual GPIO control, sequence, or UART device") - + # NEW: validation for upload-only mode + if args.upload_only: + if not args.uart_device or not args.firmware: + parser.error("--upload-only requires UART device and firmware file") + else: + if not any([args.manual_srst, args.manual_fwspick, args.sequence, args.uart_device]): + parser.error("Must specify either manual GPIO control, sequence, or UART device") + if args.uart_device and not args.skip_uart and not Path(args.uart_device).exists(): parser.error(f"UART device not found: {args.uart_device}") @@ -443,6 +454,15 @@ def main(): state = "dh" if args.manual_fwspick in ['high', 'dh'] else "dl" executor.toggle_fwspick(state) return 0 + + # NEW: upload-only execution path (no GPIO, no monitoring) + if args.upload_only: + if not executor.open_serial(): + return 1 + # Directly upload firmware, skip GPIO and waiting/monitoring + ok = executor.upload_firmware() + executor.cleanup() + return 0 if ok else 1 # Handle sequence operations if args.sequence == 'fwspick-mode': From bd56a688b1306b2c75f0abb1e62c0c113f7bf614 Mon Sep 17 00:00:00 2001 From: Will Marone Date: Mon, 15 Dec 2025 16:18:36 -0800 Subject: [PATCH 9/9] document the CI flow --- docs/ci-aspeed.md | 150 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 docs/ci-aspeed.md diff --git a/docs/ci-aspeed.md b/docs/ci-aspeed.md new file mode 100644 index 0000000..6e0667b --- /dev/null +++ b/docs/ci-aspeed.md @@ -0,0 +1,150 @@ +# CI Setup for the ASPEED AST1060 + EVB A3 + +## Overview + +The ASPEED AST1060 EVB A3 features two AST1060 evaluation boards linked over a larger test fixture. One device is equipped with a JTAG pigtail on the underside of the board. + +Control of the AST1060 for testing purposes requires both boards, but only one needs to be loaded regularly. It is recommended that the secondary board (without JTAG) have an appropriate image written to SPI. + +## Resources + +To set up CI for the AST1060, the following devices will be needed: + +* A Raspberry Pi 4 or 5 +* An ASpeed PRoT Fixture vA3.0 with two EVB cards. +* 2x USB UART adapters + * Recommended: https://www.amazon.com/DTECH-Adapter-Compatible-Windows-Genuine/dp/B0D97VR3CY/ + * Any M USB UART adapter will work as well. +* 4x F-to-F fly leads + +## Setup + +### Raspberry Pi + +#### OS + +Install the latest Raspberry Pi OS on a uSD card. Do not enable SPI or I2C. + +Recommended packages: xxd, picocom, ack, libglib2.0-dev, liblua5.2-dev, tio + +### EVB + +Ensure power is supplied to the board and the USB UART cables are plugged in. + +### Hardware Configuration + +* Connect the USB uart cables to the UART header on each EVB. Plug these into the Raspberry Pi and note their path via /dev/serial/by-id/uart_... + * Alternatively, just plug in the DB-9 +* Using an F-to-F fly lead, connect RPi pin 12 (GPIO 18) to pin 2 of J1 (FWSPICK) +* Using an F-to-F fly lead, connect RPi pin 16 (GPIO 23) to pin 1 of J3 (SRST) + +## Testing + +All builds and tests are executed by the Raspberry Pi using a support script within the repository. + +### Build + +Build is managed via Cargo + +``` +\$ cargo build +``` + +### Package + +First, we dump the ELF to a binary file: + +``` +$ cargo objcopy -- -O binary ast10x0.bin +``` + +The AST1060 UART requires a 4-byte header that informs the ROM of the size of the incoming payload. + +``` + ./scripts/gen_uart_booting_image.sh ast10x0.bin uart_ast10x0.bin + ``` + +### Execute + +A support script in the repository, uart-test-exec.py, is designed to give both high level test contorl and fine-grained device control. + +``` +$ python3 uart-test-exec.py --help + +usage: uart-test-exec.py [-h] [--srst-pin SRST_PIN] [--fwspick-pin FWSPICK_PIN] [--manual-srst {low,high,dl,dh}] + [--manual-fwspick {low,high,dl,dh}] [--sequence {fwspick-mode,normal-mode}] [-b BAUDRATE] + [--test-timeout TEST_TIMEOUT] [--log-file LOG_FILE] [--skip-uart] [-q] [--dry-run] + [uart_device] [firmware] + +AST1060 UART Test Execution Script + +positional arguments: + uart_device UART device path (e.g., /dev/ttyUSB0) + firmware Firmware binary file path + +options: + -h, --help show this help message and exit + --srst-pin SRST_PIN SRST GPIO pin number (default: 23) + --fwspick-pin FWSPICK_PIN + FWSPICK GPIO pin number (default: 18) + --manual-srst {low,high,dl,dh} + Manually toggle SRST pin + --manual-fwspick {low,high,dl,dh} + Manually toggle FWSPICK pin + --sequence {fwspick-mode,normal-mode} + Run GPIO sequence + -b BAUDRATE, --baudrate BAUDRATE + UART baud rate (default: 115200) + --test-timeout TEST_TIMEOUT + Test execution monitoring timeout in seconds (default: 600) + --log-file LOG_FILE Log file path (auto-generated if not specified) + --skip-uart Skip all UART operations + -q, --quiet Run silently (no output) + --dry-run Show what would be done without executing + +Examples: + # Full test sequence + ./uart-test-exec.py /dev/ttyUSB0 firmware.bin + + # Manual GPIO control + ./uart-test-exec.py --manual-srst low + ./uart-test-exec.py --manual-fwspick high + + # Sequence control + ./uart-test-exec.py --sequence fwspick-mode + ./uart-test-exec.py --sequence normal-mode + + # Custom pin numbers and timeout + ./uart-test-exec.py --srst-pin 25 --fwspick-pin 20 --test-timeout 300 /dev/ttyUSB0 firmware.bin +``` + +To execute the test: + +``` +$ python3 uart-test-exec.py /dev/serial/by-id/usb-... uart_ast10x0.bin +``` + +The script will execute the following sequence: +- Assert SRST# +- Ensure FWSPICK is asserted (J1) +- Deassert SRST# +- Read uart waiting for "U" +- Upload the firmware +- Read UART output until "COMPLETE" is seen or a timeout happens. + +While running the script looks for three tokens: + +"panic" - The test executable has suffered an unrecoverable fault +"PASS" - A test has completed successfully +"FAIL" - A test has failed + +If "panic" or "FAIL" is seen anywhere in the test, the script will print all UART output and return non-zero. Otherwise the script will return zero. + +## Automation + +When using a Github runner, it is recommended that the following things be set in environment variables and passed in via the YAML: +- UART device for the EVB +- SRST GPIO pin +- FWSPICK GPIO pin + +The last two can be overridden on the command-line, allowing the script to operate automatically against multiple devices connected to the same Raspberry Pi. \ No newline at end of file