مقایسه کامیتها
7
کامیتها
3c470a8fea
..
main
| مولف | SHA1 | تاریخ | |
|---|---|---|---|
|
|
5be306ad1f | ||
|
|
fc45c88708 | ||
|
|
68583fd185 | ||
|
|
0528032639 | ||
|
|
9b5bff8002 | ||
|
|
b38dd9baf8 | ||
|
|
cdf22287fe |
@@ -22,7 +22,7 @@ jobs:
|
|||||||
CONFIRM_SQUASHFS_MTDBLOCK3: ${{ inputs.confirm_squashfs_mtdblock3 }}
|
CONFIRM_SQUASHFS_MTDBLOCK3: ${{ inputs.confirm_squashfs_mtdblock3 }}
|
||||||
run: |
|
run: |
|
||||||
if [ "$CONFIRM_SQUASHFS_MTDBLOCK3" = "true" ]; then
|
if [ "$CONFIRM_SQUASHFS_MTDBLOCK3" = "true" ]; then
|
||||||
/usr/bin/python3 scripts/serial_command.py boot-config --confirm-squashfs-mtdblock3
|
/usr/bin/python3 deployment/serial/serial_command.py boot-config --confirm-squashfs-mtdblock3
|
||||||
else
|
else
|
||||||
/usr/bin/python3 scripts/serial_command.py boot-config
|
/usr/bin/python3 deployment/serial/serial_command.py boot-config
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Detect flash, set bootcmd and verify
|
- name: Detect flash, set bootcmd and verify
|
||||||
run: /usr/bin/python3 scripts/serial_command.py set-bootcmd
|
run: /usr/bin/python3 deployment/serial/serial_command.py set-bootcmd
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Transfer DTB, write SPI flash and verify read-back
|
- name: Transfer DTB, write SPI flash and verify read-back
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/serial_command.py transfer-dtb
|
/usr/bin/python3 deployment/serial/serial_command.py transfer-dtb
|
||||||
|
|||||||
@@ -38,34 +38,41 @@ jobs:
|
|||||||
PY
|
PY
|
||||||
|
|
||||||
- name: Check current U-Boot CLI
|
- name: Check current U-Boot CLI
|
||||||
run: /usr/bin/python3 scripts/serial_command.py check
|
run: /usr/bin/python3 deployment/serial/serial_command.py check
|
||||||
|
|
||||||
- name: Configure and verify network
|
- name: Configure and verify network
|
||||||
run: /usr/bin/python3 scripts/serial_command.py configure
|
env:
|
||||||
|
UBOOT_DEVICE_IP: ${{ vars.UBOOT_DEVICE_IP || '192.168.75.100' }}
|
||||||
|
TFTP_SERVER_IP: ${{ vars.TFTP_SERVER_IP || '192.168.75.247' }}
|
||||||
|
run: |
|
||||||
|
/usr/bin/python3 deployment/serial/serial_command.py configure --device-ip "$UBOOT_DEVICE_IP" --tftp-server-ip "$TFTP_SERVER_IP"
|
||||||
|
|
||||||
- name: Check all firmware sizes against detected flash layout
|
- name: Check all firmware sizes against detected flash layout
|
||||||
run: /usr/bin/python3 scripts/serial_command.py preflight
|
run: /usr/bin/python3 deployment/serial/serial_command.py preflight
|
||||||
|
|
||||||
- name: Erase detected SPI flash and verify sample
|
- name: Erase detected SPI flash and verify sample
|
||||||
run: /usr/bin/python3 scripts/serial_command.py erase
|
run: /usr/bin/python3 deployment/serial/serial_command.py erase
|
||||||
|
|
||||||
- name: Transfer, write and verify U-Boot
|
- name: Transfer, write and verify U-Boot
|
||||||
run: /usr/bin/python3 scripts/serial_command.py transfer
|
run: /usr/bin/python3 deployment/serial/serial_command.py transfer
|
||||||
|
|
||||||
- name: Transfer, write and verify DTB
|
- name: Transfer, write and verify DTB
|
||||||
run: /usr/bin/python3 scripts/serial_command.py transfer-dtb
|
run: /usr/bin/python3 deployment/serial/serial_command.py transfer-dtb
|
||||||
|
|
||||||
- name: Transfer, write and verify Kernel
|
- name: Transfer, write and verify Kernel
|
||||||
run: /usr/bin/python3 scripts/serial_command.py transfer-kernel
|
run: /usr/bin/python3 deployment/serial/serial_command.py transfer-kernel
|
||||||
|
|
||||||
- name: Transfer, write and verify RootFS
|
- name: Transfer, write and verify RootFS
|
||||||
run: /usr/bin/python3 scripts/serial_command.py transfer-rootfs
|
run: /usr/bin/python3 deployment/serial/serial_command.py transfer-rootfs
|
||||||
|
|
||||||
- name: Inspect boot configuration
|
- name: Inspect boot configuration
|
||||||
run: /usr/bin/python3 scripts/serial_command.py boot-config
|
run: /usr/bin/python3 deployment/serial/serial_command.py boot-config
|
||||||
|
|
||||||
- name: Configure and verify bootcmd for detected flash
|
- name: Configure and verify bootcmd for detected flash
|
||||||
run: /usr/bin/python3 scripts/serial_command.py set-bootcmd
|
run: /usr/bin/python3 deployment/serial/serial_command.py set-bootcmd
|
||||||
|
|
||||||
|
# - name: Save U-Boot environment before reboot
|
||||||
|
# run: /usr/bin/python3 deployment/serial/serial_command.py saveenv
|
||||||
|
|
||||||
- name: Reboot hardware after successful flashing
|
- name: Reboot hardware after successful flashing
|
||||||
run: /usr/bin/python3 scripts/serial_command.py reboot
|
run: /usr/bin/python3 deployment/serial/serial_command.py reboot
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Erase entire flash and verify first 256 bytes
|
- name: Erase entire flash and verify first 256 bytes
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/serial_command.py erase
|
/usr/bin/python3 deployment/serial/serial_command.py erase
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: init test
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
init-test:
|
||||||
|
runs-on: serial-runner
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Verify ordered boot and Linux console
|
||||||
|
env:
|
||||||
|
RUTBS_PASSWORD: ${{ secrets.RUTBS_PASSWORD }}
|
||||||
|
run: /usr/bin/python3 deployment/serial/init_test.py
|
||||||
|
|
||||||
|
- name: Upload redacted serial log
|
||||||
|
if: ${{ always() }}
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: boot-log
|
||||||
|
path: artifacts/boot.log
|
||||||
|
if-no-files-found: error
|
||||||
@@ -13,4 +13,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Transfer Kernel, write SPI flash and verify read-back
|
- name: Transfer Kernel, write SPI flash and verify read-back
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/serial_command.py transfer-kernel
|
/usr/bin/python3 deployment/serial/serial_command.py transfer-kernel
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Detect flash, transfer RootFS and verify read-back
|
- name: Detect flash, transfer RootFS and verify read-back
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/serial_command.py transfer-rootfs
|
/usr/bin/python3 deployment/serial/serial_command.py transfer-rootfs
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Check current U-Boot CLI prompt
|
- name: Check current U-Boot CLI prompt
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/serial_command.py check
|
/usr/bin/python3 deployment/serial/serial_command.py check
|
||||||
|
|||||||
@@ -27,4 +27,4 @@ jobs:
|
|||||||
TEST_HOST_IP: ${{ vars.TEST_HOST_IP || '192.168.75.15' }}
|
TEST_HOST_IP: ${{ vars.TEST_HOST_IP || '192.168.75.15' }}
|
||||||
TEST_DEVICE_IP: ${{ vars.TEST_DEVICE_IP }}
|
TEST_DEVICE_IP: ${{ vars.TEST_DEVICE_IP }}
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/software_test.py --interface "$TEST_INTERFACE" --host-ip "$TEST_HOST_IP" --device-ip "$TEST_DEVICE_IP"
|
/usr/bin/python3 deployment/serial/software_test.py --interface "$TEST_INTERFACE" --host-ip "$TEST_HOST_IP" --device-ip "$TEST_DEVICE_IP"
|
||||||
|
|||||||
@@ -12,5 +12,8 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Configure network and verify U-Boot commands
|
- name: Configure network and verify U-Boot commands
|
||||||
|
env:
|
||||||
|
UBOOT_DEVICE_IP: ${{ vars.UBOOT_DEVICE_IP || '192.168.75.100' }}
|
||||||
|
TFTP_SERVER_IP: ${{ vars.TFTP_SERVER_IP || '192.168.75.247' }}
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/serial_command.py configure
|
/usr/bin/python3 deployment/serial/serial_command.py configure --device-ip "$UBOOT_DEVICE_IP" --tftp-server-ip "$TFTP_SERVER_IP"
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Transfer firmware, write SPI flash and verify read-back
|
- name: Transfer firmware, write SPI flash and verify read-back
|
||||||
run: |
|
run: |
|
||||||
/usr/bin/python3 scripts/serial_command.py transfer
|
/usr/bin/python3 deployment/serial/serial_command.py transfer
|
||||||
|
|||||||
+68
-1
@@ -3,6 +3,29 @@
|
|||||||
workflow اصلی `flash-aminpa-V1.5.yml` با push به شاخهٔ `main` اجرا میشود.
|
workflow اصلی `flash-aminpa-V1.5.yml` با push به شاخهٔ `main` اجرا میشود.
|
||||||
workflow تست نرمافزاری برای اجرا پس از موفقیت آن و همچنین اجرای دستی تعریف شده است.
|
workflow تست نرمافزاری برای اجرا پس از موفقیت آن و همچنین اجرای دستی تعریف شده است.
|
||||||
|
|
||||||
|
## متغیرهای Gitea برای شبکهٔ U-Boot و TFTP
|
||||||
|
|
||||||
|
این متغیرها را در تنظیمات مخزن، بخش **Actions → Variables** تعریف کنید.
|
||||||
|
workflow تنظیم شبکه و workflow اصلی فلش از آنها استفاده میکنند.
|
||||||
|
|
||||||
|
| نام متغیر | کاربرد | پیشفرض |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `UBOOT_DEVICE_IP` | IP دستگاه در U-Boot؛ مقدار `ipaddr` | `192.168.75.100` |
|
||||||
|
| `TFTP_SERVER_IP` | IP سرور TFTP؛ مقدار `serverip` و مقصد ping بررسی شبکه | `192.168.75.247` |
|
||||||
|
|
||||||
|
هر دو مقدار باید IPv4 معتبر و بدون CIDR باشند. اگر متغیر تعریف نشود،
|
||||||
|
مقدار پیشفرض استفاده میشود. مقدار تنظیمشده روی برد بازخوانی و بررسی میشود.
|
||||||
|
`UBOOT_DEVICE_IP` مربوط به مرحلهٔ فلش است؛ `TEST_DEVICE_IP` مربوط به دستگاه
|
||||||
|
پس از بوت سیستمعامل است و میتواند متفاوت باشد.
|
||||||
|
|
||||||
|
نمونهٔ اجرای مستقیم:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/usr/bin/python3 deployment/serial/serial_command.py configure \
|
||||||
|
--device-ip 192.168.75.100 \
|
||||||
|
--tftp-server-ip 192.168.75.247
|
||||||
|
```
|
||||||
|
|
||||||
## متغیرهای Gitea برای تست نرمافزاری
|
## متغیرهای Gitea برای تست نرمافزاری
|
||||||
|
|
||||||
متغیرها را در تنظیمات مخزن Gitea، بخش **Actions → Variables** تعریف کنید.
|
متغیرها را در تنظیمات مخزن Gitea، بخش **Actions → Variables** تعریف کنید.
|
||||||
@@ -30,10 +53,54 @@ IP میزبان باید از قبل روی کارت شبکهٔ انتخاب
|
|||||||
نمونهٔ اجرای دستی روی runner با جایگزینکردن IP واقعی دستگاه:
|
نمونهٔ اجرای دستی روی runner با جایگزینکردن IP واقعی دستگاه:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/usr/bin/python3 scripts/software_test.py \
|
/usr/bin/python3 deployment/serial/software_test.py \
|
||||||
--interface eno1 \
|
--interface eno1 \
|
||||||
--host-ip 192.168.75.15 \
|
--host-ip 192.168.75.15 \
|
||||||
--device-ip <DEVICE_IP>
|
--device-ip <DEVICE_IP>
|
||||||
```
|
```
|
||||||
|
|
||||||
برای محل فایلهای firmware و نقشهٔ فلش، [راهنمای firmware](firmware/README.md) را ببینید.
|
برای محل فایلهای firmware و نقشهٔ فلش، [راهنمای firmware](firmware/README.md) را ببینید.
|
||||||
|
|
||||||
|
## ساختار استقرار
|
||||||
|
|
||||||
|
اسکریپتهای اجرایی در `deployment/serial/` قرار دارند:
|
||||||
|
|
||||||
|
- `serial_command.py`: کنترل U-Boot، فلش و راهاندازی برد از طریق سریال.
|
||||||
|
- `software_test.py`: بررسی پاسخ ping دستگاه پس از بوت.
|
||||||
|
|
||||||
|
workflowهای اصلی امینپرداز و ورودیهای workflowهای کمکی در
|
||||||
|
`.gitea/workflows/` باقی میمانند و اسکریپتها را از مسیر جدید اجرا میکنند.
|
||||||
|
فایلهای باینری همچنان در پوشهٔ `firmware/` ریشهٔ مخزن قرار میگیرند.
|
||||||
|
|
||||||
|
## تست ترتیب بوت از طریق Serial
|
||||||
|
|
||||||
|
workflow دستی `init test` از فایل `.gitea/workflows/init-test.yml` اجرا میشود.
|
||||||
|
رمز دستگاه را با نام `RUTBS_PASSWORD` در **Actions → Secrets** مخزن تعریف کنید؛
|
||||||
|
رمز در سورس یا متغیرهای عمومی قرار نمیگیرد. رانر به `pyserial` و دسترسی
|
||||||
|
به پورت `/dev/ttyUSB0` نیاز دارد.
|
||||||
|
|
||||||
|
برای اجرای مستقیم، رمز را بدون نمایش در ترمینال دریافت کنید:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
read -rs -p 'RUTBS password: ' RUTBS_PASSWORD
|
||||||
|
export RUTBS_PASSWORD
|
||||||
|
python3 deployment/serial/init_test.py
|
||||||
|
unset RUTBS_PASSWORD
|
||||||
|
```
|
||||||
|
|
||||||
|
برای پورت دیگر، گزینهٔ `--port /dev/ttyUSB1` را اضافه کنید.
|
||||||
|
برای مشاهدهٔ زندهٔ خروجی Serial با حذف رمز، گزینهٔ `--verbose` یا `-v` را اضافه کنید:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 deployment/serial/init_test.py --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
هنگام اجرای تست، برد باید در پرامپت U-Boot یعنی `=>` باشد.
|
||||||
|
اسکریپت ابتدا با ارسال Enter پرامپت را تازه میکند و پس از تشخیص آن،
|
||||||
|
دستور `res` را برای ریبوت میفرستد؛ سپس مراحل بوت را بهترتیب بررسی میکند.
|
||||||
|
این تست باید پیامهای انتخاب سطح دیباگ و فعالسازی کنسول را ببیند و
|
||||||
|
با workflow دیگری که همان پورت Serial را استفاده میکند همزمان اجرا نشود.
|
||||||
|
از شروع تست، مهلت مشترک مراحل ۱۸۰ ثانیه است. تنها پس از ورود و دریافت
|
||||||
|
خروجی دقیق `Linux` از `uname -s`، پیام `BOOT TEST: PASS` و کد خروج صفر
|
||||||
|
برگردانده میشود. خطا کد خروج غیرصفر دارد. لاگ Serial با حذف رمز در
|
||||||
|
`artifacts/boot.log` ذخیره و حتی در شکست تست بهصورت آرتیفکت بارگذاری میشود.
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
"""Restart RUTBS from U-Boot and verify the ordered boot and kernel name."""
|
||||||
|
import argparse
|
||||||
|
import fcntl
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
|
||||||
|
STAGES = (
|
||||||
|
('U-Boot prompt', '=>', b'res\r'),
|
||||||
|
('kernel', 'Starting kernel ...', None),
|
||||||
|
('debug selection', 'Press the [1], [2], [3] or [4] key and hit [enter] to select the debug level', None),
|
||||||
|
('console activation', 'Please press Enter to activate this console.', b'\r'),
|
||||||
|
('login', 'RUTBS login:', b'root\r'),
|
||||||
|
('password', 'Password:', 'password'),
|
||||||
|
('shell', 'root@RUTBS:~#', b'uname -s\r'),
|
||||||
|
('command result', 'root@RUTBS:~#', None),
|
||||||
|
)
|
||||||
|
FAILURE = re.compile(r'Kernel panic|Login incorrect|Login failed|Authentication fail(?:ed|ure)|Access denied', re.I)
|
||||||
|
|
||||||
|
|
||||||
|
def run_test(port, password, transcript, deadline):
|
||||||
|
stage = 0
|
||||||
|
pending = ''
|
||||||
|
observed = ''
|
||||||
|
# Request a fresh prompt when the existing => predates opening the port.
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0:
|
||||||
|
raise RuntimeError('Overall test timeout')
|
||||||
|
port.write_timeout = min(2, remaining)
|
||||||
|
if port.write(b'\r') != 1:
|
||||||
|
raise RuntimeError('Incomplete serial write')
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
data = port.read(min(port.in_waiting or 1, 4096))
|
||||||
|
if not data:
|
||||||
|
continue
|
||||||
|
transcript.extend(data)
|
||||||
|
chunk = data.decode('utf-8', errors='replace').replace('\r', '')
|
||||||
|
pending += chunk
|
||||||
|
observed = (observed + chunk)[-8192:]
|
||||||
|
if FAILURE.search(observed):
|
||||||
|
raise RuntimeError('Boot or authentication failure detected')
|
||||||
|
while stage < len(STAGES):
|
||||||
|
name, token, action = STAGES[stage]
|
||||||
|
if stage == 0:
|
||||||
|
match = re.search(r'(?:^|\n)[^\n]*=> *$', pending)
|
||||||
|
position = pending.rfind('=>') if match else -1
|
||||||
|
else:
|
||||||
|
position = pending.find(token)
|
||||||
|
if position < 0:
|
||||||
|
break
|
||||||
|
before = pending[:position]
|
||||||
|
pending = pending[position + len(token):]
|
||||||
|
if stage == len(STAGES) - 1:
|
||||||
|
lines = [line.strip() for line in before.splitlines() if line.strip()]
|
||||||
|
if lines and lines[0] == 'uname -s':
|
||||||
|
lines.pop(0) # A terminal may echo the command.
|
||||||
|
if lines != ['Linux']:
|
||||||
|
raise RuntimeError('uname -s output was not exactly Linux')
|
||||||
|
if time.monotonic() >= deadline:
|
||||||
|
raise RuntimeError('Overall test timeout')
|
||||||
|
return
|
||||||
|
if action is not None:
|
||||||
|
# Discard pre-existing input: the next prompt must follow our write.
|
||||||
|
pending = ''
|
||||||
|
payload = password.encode('utf-8') + b'\r' if action == 'password' else action
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0:
|
||||||
|
raise RuntimeError('Overall test timeout')
|
||||||
|
port.write_timeout = min(2, remaining)
|
||||||
|
if port.write(payload) != len(payload):
|
||||||
|
raise RuntimeError('Incomplete serial write')
|
||||||
|
stage += 1
|
||||||
|
if len(pending) > 65536:
|
||||||
|
raise RuntimeError('Unexpected excessive serial output in current stage')
|
||||||
|
raise RuntimeError('Timeout waiting for ' + STAGES[stage][0])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--port', default='/dev/ttyUSB0')
|
||||||
|
args = parser.parse_args()
|
||||||
|
deadline = time.monotonic() + 180
|
||||||
|
password = os.environ.get('RUTBS_PASSWORD', '')
|
||||||
|
transcript = bytearray()
|
||||||
|
result = 1
|
||||||
|
reason = 'Serial connection or configuration error'
|
||||||
|
log = Path('artifacts/boot.log')
|
||||||
|
try:
|
||||||
|
log.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
# Never put raw serial bytes on disk or stdout, even during authentication.
|
||||||
|
log.write_text('', encoding='utf-8')
|
||||||
|
if not password or any(c in password for c in '\r\n\x00'):
|
||||||
|
raise RuntimeError('RUTBS_PASSWORD is missing or invalid')
|
||||||
|
with open('/tmp/uboot-serial.lock', 'w') as lock:
|
||||||
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
|
with serial.Serial(args.port, 115200, timeout=0.1, write_timeout=2, exclusive=True) as port:
|
||||||
|
run_test(port, password, transcript, deadline)
|
||||||
|
result = 0
|
||||||
|
except RuntimeError as exc:
|
||||||
|
reason = str(exc)
|
||||||
|
except Exception:
|
||||||
|
# Serial/library exceptions can include transmitted data; do not print them.
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
content = transcript.decode('utf-8', errors='replace').replace('\r', '')
|
||||||
|
if password:
|
||||||
|
content = content.replace(password, '[REDACTED]')
|
||||||
|
reason = reason.replace(password, '[REDACTED]')
|
||||||
|
try:
|
||||||
|
log.write_text(content, encoding='utf-8')
|
||||||
|
except OSError:
|
||||||
|
result = 1
|
||||||
|
reason = 'Could not save serial log'
|
||||||
|
if result:
|
||||||
|
print('BOOT TEST: FAIL')
|
||||||
|
print(reason)
|
||||||
|
else:
|
||||||
|
print('BOOT TEST: PASS')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Check an existing U-Boot prompt or configure its volatile environment."""
|
"""Check an existing U-Boot prompt or configure its volatile environment."""
|
||||||
import argparse
|
import argparse
|
||||||
|
import ipaddress
|
||||||
import fcntl
|
import fcntl
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -126,12 +127,21 @@ def transfer(cli, firmware, *, dtb=False, kernel=False, rootfs=False, rootfs_siz
|
|||||||
r"^Bytes transferred = \d+", timeout=120,
|
r"^Bytes transferred = \d+", timeout=120,
|
||||||
)
|
)
|
||||||
transferred = re.findall(r"^Bytes transferred = (\d+)", output, re.MULTILINE)
|
transferred = re.findall(r"^Bytes transferred = (\d+)", output, re.MULTILINE)
|
||||||
if transferred != [str(len(image))]:
|
# TEMPORARY: accept the TFTP image even when the checkout has another version.
|
||||||
raise RuntimeError("TFTP byte count differs from the local firmware file")
|
# if transferred != [str(len(image))]:
|
||||||
|
# raise RuntimeError("TFTP byte count differs from the local firmware file")
|
||||||
|
if len(transferred) != 1:
|
||||||
|
raise RuntimeError("Missing or ambiguous TFTP byte count")
|
||||||
|
transfer_size = int(transferred[0])
|
||||||
|
if not dump_size <= transfer_size <= limit:
|
||||||
|
raise RuntimeError("TFTP image size exceeds the selected partition or is invalid")
|
||||||
|
if rootfs and transfer_size > 0x1800000:
|
||||||
|
raise RuntimeError("TFTP RootFS exceeds the non-overlapping RAM buffer capacity")
|
||||||
|
print(f"Using TFTP image: {transfer_size} bytes; local image identity check disabled.")
|
||||||
output = cli.command("echo ${filesize}", r"^[0-9a-fA-F]+$")
|
output = cli.command("echo ${filesize}", r"^[0-9a-fA-F]+$")
|
||||||
sizes = re.findall(r"^([0-9a-fA-F]+)$", output, re.MULTILINE)
|
sizes = re.findall(r"^([0-9a-fA-F]+)$", output, re.MULTILINE)
|
||||||
if len(sizes) != 1 or int(sizes[0], 16) != len(image):
|
if len(sizes) != 1 or int(sizes[0], 16) != transfer_size:
|
||||||
raise RuntimeError("U-Boot filesize differs from the local firmware file")
|
raise RuntimeError("U-Boot filesize differs from the TFTP transfer byte count")
|
||||||
output = cli.command(f"md.b {address:#x} {dump_size:#x}", rf"^{address:08x}:")
|
output = cli.command(f"md.b {address:#x} {dump_size:#x}", rf"^{address:08x}:")
|
||||||
dumped = bytearray()
|
dumped = bytearray()
|
||||||
for row_offset in range(0, dump_size, 16):
|
for row_offset in range(0, dump_size, 16):
|
||||||
@@ -140,23 +150,24 @@ def transfer(cli, firmware, *, dtb=False, kernel=False, rootfs=False, rootfs_siz
|
|||||||
if len(rows) != 1:
|
if len(rows) != 1:
|
||||||
raise RuntimeError("Missing or invalid memory dump at " + row_address)
|
raise RuntimeError("Missing or invalid memory dump at " + row_address)
|
||||||
dumped.extend(bytes.fromhex(rows[0]))
|
dumped.extend(bytes.fromhex(rows[0]))
|
||||||
if dumped != image[:dump_size]:
|
# TEMPORARY: the TFTP image may also have a different header from the checkout.
|
||||||
raise RuntimeError(f"First {dump_size} bytes in RAM differ from the local firmware file")
|
# if dumped != image[:dump_size]:
|
||||||
print(f"\nTransfer verified: size and first {dump_size} bytes match.")
|
# raise RuntimeError(f"First {dump_size} bytes in RAM differ from the local firmware file")
|
||||||
|
print("\nTransfer size verified against U-Boot filesize; RAM header read successfully.")
|
||||||
cli.command(
|
cli.command(
|
||||||
f"sf write {address:#x} {offset:#x} ${{filesize}}",
|
f"sf write {address:#x} {offset:#x} ${{filesize}}",
|
||||||
rf"^SF: {len(image)} bytes @ {offset:#x} Written: OK\s*$", timeout=120,
|
rf"^SF: {transfer_size} bytes @ {offset:#x} Written: OK\s*$", timeout=120,
|
||||||
)
|
)
|
||||||
cli.command(
|
cli.command(
|
||||||
f"sf read 0x42800000 {offset:#x} ${{filesize}}",
|
f"sf read 0x42800000 {offset:#x} ${{filesize}}",
|
||||||
rf"^SF: {len(image)} bytes @ {offset:#x} Read: OK\s*$", timeout=120,
|
rf"^SF: {transfer_size} bytes @ {offset:#x} Read: OK\s*$", timeout=120,
|
||||||
)
|
)
|
||||||
output = cli.command(
|
output = cli.command(
|
||||||
f"cmp.b {address:#x} 0x42800000 ${{filesize}}",
|
f"cmp.b {address:#x} 0x42800000 ${{filesize}}",
|
||||||
r"^Total of \d+ byte\(s\) were the same", timeout=60,
|
r"^Total of \d+ byte\(s\) were the same", timeout=60,
|
||||||
)
|
)
|
||||||
compared = re.findall(r"^Total of (\d+) byte\(s\) were the same", output, re.MULTILINE)
|
compared = re.findall(r"^Total of (\d+) byte\(s\) were the same", output, re.MULTILINE)
|
||||||
if compared != [str(len(image))]:
|
if compared != [str(transfer_size)]:
|
||||||
raise RuntimeError("Flash comparison byte count differs from firmware size")
|
raise RuntimeError("Flash comparison byte count differs from firmware size")
|
||||||
print("\nFirmware flash write and full read-back comparison verified.")
|
print("\nFirmware flash write and full read-back comparison verified.")
|
||||||
|
|
||||||
@@ -205,7 +216,7 @@ def boot_config(cli, confirmed=False):
|
|||||||
if not confirmed:
|
if not confirmed:
|
||||||
print("\nBoot configuration inspected; no changes requested.")
|
print("\nBoot configuration inspected; no changes requested.")
|
||||||
return
|
return
|
||||||
bootargs = "console=ttyS0,115200 loglevel=8 root=/dev/mtdblock3 rootfstype=squashfs ro rootwait"
|
bootargs = "console=ttyS0,115200 loglevel=1 root=/dev/mtdblock3 rootfstype=squashfs ro rootwait"
|
||||||
cli.command("setenv bootargs '" + bootargs + "'")
|
cli.command("setenv bootargs '" + bootargs + "'")
|
||||||
cli.command("printenv bootargs", r"^bootargs=" + re.escape(bootargs) + r"$")
|
cli.command("printenv bootargs", r"^bootargs=" + re.escape(bootargs) + r"$")
|
||||||
print("\nBootargs verified in RAM; environment not saved and board not rebooted.")
|
print("\nBootargs verified in RAM; environment not saved and board not rebooted.")
|
||||||
@@ -213,15 +224,17 @@ def boot_config(cli, confirmed=False):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs", "erase", "boot-config", "preflight", "set-bootcmd", "reboot"), default="check", nargs="?")
|
parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs", "erase", "boot-config", "preflight", "set-bootcmd", "saveenv", "reboot"), default="check", nargs="?")
|
||||||
parser.add_argument("--port", default="/dev/ttyUSB0")
|
parser.add_argument("--port", default="/dev/ttyUSB0")
|
||||||
parser.add_argument("--firmware", type=Path, default=None)
|
parser.add_argument("--firmware", type=Path, default=None)
|
||||||
parser.add_argument("--rootfs-size", type=lambda value: int(value, 0), help="Actual RootFS partition size in bytes (decimal or 0x-prefixed hex)")
|
parser.add_argument("--rootfs-size", type=lambda value: int(value, 0), help="Actual RootFS partition size in bytes (decimal or 0x-prefixed hex)")
|
||||||
parser.add_argument("--confirm-squashfs-mtdblock3", action="store_true", help="Confirm actual RootFS is SquashFS on mtdblock3 and set bootargs")
|
parser.add_argument("--confirm-squashfs-mtdblock3", action="store_true", help="Confirm actual RootFS is SquashFS on mtdblock3 and set bootargs")
|
||||||
|
parser.add_argument("--device-ip", type=ipaddress.IPv4Address, default="192.168.75.100")
|
||||||
|
parser.add_argument("--tftp-server-ip", type=ipaddress.IPv4Address, default="192.168.75.247")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.firmware is None:
|
if args.firmware is None:
|
||||||
filename = "rootfs.squashfs" if args.mode == "transfer-rootfs" else "zImage" if args.mode == "transfer-kernel" else ("sun8i-v3s-licheepi-zero-dock.dtb" if args.mode == "transfer-dtb" else "u-boot-sunxi-with-spl.bin")
|
filename = "rootfs.squashfs" if args.mode == "transfer-rootfs" else "zImage" if args.mode == "transfer-kernel" else ("sun8i-v3s-licheepi-zero-dock.dtb" if args.mode == "transfer-dtb" else "u-boot-sunxi-with-spl.bin")
|
||||||
args.firmware = Path(__file__).resolve().parent.parent / "firmware" / filename
|
args.firmware = Path(__file__).resolve().parents[2] / "firmware" / filename
|
||||||
if args.mode in ("transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs") and not args.firmware.is_file():
|
if args.mode in ("transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs") and not args.firmware.is_file():
|
||||||
raise RuntimeError("Firmware file not found: " + str(args.firmware))
|
raise RuntimeError("Firmware file not found: " + str(args.firmware))
|
||||||
# Shared by both workflows: fail rather than interleave serial commands.
|
# Shared by both workflows: fail rather than interleave serial commands.
|
||||||
@@ -232,6 +245,10 @@ def main():
|
|||||||
cli.detect()
|
cli.detect()
|
||||||
if args.mode == "check":
|
if args.mode == "check":
|
||||||
return
|
return
|
||||||
|
if args.mode == "saveenv":
|
||||||
|
cli.command("saveenv", timeout=120)
|
||||||
|
print("Current U-Boot environment saved successfully.", flush=True)
|
||||||
|
return
|
||||||
if args.mode == "reboot":
|
if args.mode == "reboot":
|
||||||
print("\n>>> reset", flush=True)
|
print("\n>>> reset", flush=True)
|
||||||
port.write(b"reset\r")
|
port.write(b"reset\r")
|
||||||
@@ -243,7 +260,7 @@ def main():
|
|||||||
files = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb",
|
files = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb",
|
||||||
"kernel": "zImage", "rootfs": "rootfs.squashfs"}
|
"kernel": "zImage", "rootfs": "rootfs.squashfs"}
|
||||||
for part, filename in files.items():
|
for part, filename in files.items():
|
||||||
path = Path(__file__).resolve().parent.parent / "firmware" / filename
|
path = Path(__file__).resolve().parents[2] / "firmware" / filename
|
||||||
minimum = 64 if part in ("kernel", "rootfs") else 32
|
minimum = 64 if part in ("kernel", "rootfs") else 32
|
||||||
limit = LAYOUTS[capacity][part][1]
|
limit = LAYOUTS[capacity][part][1]
|
||||||
if not path.is_file() or not minimum <= path.stat().st_size <= limit:
|
if not path.is_file() or not minimum <= path.stat().st_size <= limit:
|
||||||
@@ -267,11 +284,11 @@ def main():
|
|||||||
"printenv ipaddr serverip ethaddr bootcmd bootargs",
|
"printenv ipaddr serverip ethaddr bootcmd bootargs",
|
||||||
allow_missing=("ipaddr", "serverip", "ethaddr", "bootcmd", "bootargs"),
|
allow_missing=("ipaddr", "serverip", "ethaddr", "bootcmd", "bootargs"),
|
||||||
)
|
)
|
||||||
cli.command("setenv ipaddr 192.168.75.100")
|
cli.command(f"setenv ipaddr {args.device_ip}")
|
||||||
cli.command("printenv ipaddr", r"^ipaddr=192\.168\.75\.100$")
|
cli.command("printenv ipaddr", r"^ipaddr=" + re.escape(str(args.device_ip)) + r"$")
|
||||||
cli.command("setenv serverip 192.168.75.247")
|
cli.command(f"setenv serverip {args.tftp_server_ip}")
|
||||||
cli.command("printenv serverip", r"^serverip=192\.168\.75\.247$")
|
cli.command("printenv serverip", r"^serverip=" + re.escape(str(args.tftp_server_ip)) + r"$")
|
||||||
cli.command("ping 192.168.75.247", r"^host 192\.168\.75\.247 is alive$", timeout=30)
|
cli.command(f"ping {args.tftp_server_ip}", r"^host " + re.escape(str(args.tftp_server_ip)) + r" is alive$", timeout=30)
|
||||||
cli.command("sf probe 0:0", r"^SF: Detected .+", timeout=20)
|
cli.command("sf probe 0:0", r"^SF: Detected .+", timeout=20)
|
||||||
print("\nAll commands verified; environment changes were not saved to flash.")
|
print("\nAll commands verified; environment changes were not saved to flash.")
|
||||||
|
|
||||||
دودویی (BIN)
فایل باینری نشان داده نشده است.
مرجع در شماره جدید
مسدود کردن کاربر