ورک فلو نصب u-boot
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
name: Transfer U-Boot to RAM
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
uboot-transfer:
|
||||||
|
runs-on: serial-runner
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Transfer firmware and verify RAM contents
|
||||||
|
run: |
|
||||||
|
/usr/bin/python3 scripts/serial_command.py transfer
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
Place `u-boot-sunxi-with-spl.bin` in this directory before running the
|
||||||
|
Transfer U-Boot to RAM workflow.
|
||||||
|
|
||||||
|
The existing TFTP server (`serverip` in U-Boot) must serve this directory
|
||||||
|
as its TFTP root, so the file is available as `u-boot-sunxi-with-spl.bin`.
|
||||||
|
Checking out this repository alone does not publish files over TFTP.
|
||||||
|
|
||||||
|
The workflow uses the current U-Boot network settings. It verifies the
|
||||||
|
transferred size and the first 32 bytes against this local file. It does
|
||||||
|
not erase or write SPI flash, save the environment, or boot the image.
|
||||||
@@ -4,6 +4,7 @@ import fcntl
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ class UBoot:
|
|||||||
self.prompt = candidate
|
self.prompt = candidate
|
||||||
print("\nU-Boot-style CLI prompt detected: " + candidate, flush=True)
|
print("\nU-Boot-style CLI prompt detected: " + candidate, flush=True)
|
||||||
|
|
||||||
def command(self, command, expected=None, timeout=10):
|
def command(self, command, expected=None, timeout=10, allow_missing=()):
|
||||||
print("\n>>> " + command, flush=True)
|
print("\n>>> " + command, flush=True)
|
||||||
self.port.write((command + "\r").encode())
|
self.port.write((command + "\r").encode())
|
||||||
self.port.flush()
|
self.port.flush()
|
||||||
@@ -65,21 +66,57 @@ class UBoot:
|
|||||||
self.port.flush()
|
self.port.flush()
|
||||||
status, _ = self.read_prompt(5)
|
status, _ = self.read_prompt(5)
|
||||||
codes = re.findall(r"(?m)^__UBOOT_STATUS_ (\d+) *$", status)
|
codes = re.findall(r"(?m)^__UBOOT_STATUS_ (\d+) *$", status)
|
||||||
if codes != ["0"]:
|
# Only the initial environment inspection may report unset variables.
|
||||||
|
missing_pattern = r'^## Error: "(' + "|".join(map(re.escape, allow_missing)) + r')" not defined$'
|
||||||
|
missing = re.findall(missing_pattern, output, re.MULTILINE) if allow_missing else []
|
||||||
|
checked_output = re.sub(missing_pattern, "", output, flags=re.MULTILINE) if missing else output
|
||||||
|
if codes != ["0"] and not (codes == ["1"] and missing):
|
||||||
raise RuntimeError("Command failed or its exit status could not be verified: " + command)
|
raise RuntimeError("Command failed or its exit status could not be verified: " + command)
|
||||||
if ERROR.search(output):
|
if ERROR.search(checked_output):
|
||||||
raise RuntimeError("Error in command response: " + command)
|
raise RuntimeError("Error in command response: " + command)
|
||||||
if expected and not re.search(expected, output, re.MULTILINE):
|
if expected and not re.search(expected, output, re.MULTILINE):
|
||||||
raise RuntimeError("Expected response missing: " + command)
|
raise RuntimeError("Expected response missing: " + command)
|
||||||
|
if missing:
|
||||||
|
print("\nInitially unset: " + ", ".join(missing), flush=True)
|
||||||
print("\nVerified: " + command, flush=True)
|
print("\nVerified: " + command, flush=True)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def transfer(cli, firmware):
|
||||||
|
image = firmware.read_bytes()
|
||||||
|
if not 32 <= len(image) <= 0x02000000:
|
||||||
|
raise RuntimeError("Firmware must contain at least 32 bytes and fit RAM at 0x42000000")
|
||||||
|
output = cli.command(
|
||||||
|
"tftp 0x42000000 u-boot-sunxi-with-spl.bin",
|
||||||
|
r"^Bytes transferred = \d+", timeout=120,
|
||||||
|
)
|
||||||
|
transferred = re.findall(r"^Bytes transferred = (\d+)", output, re.MULTILINE)
|
||||||
|
if transferred != [str(len(image))]:
|
||||||
|
raise RuntimeError("TFTP byte count differs from the local firmware file")
|
||||||
|
output = cli.command("echo ${filesize}", r"^[0-9a-fA-F]+$")
|
||||||
|
sizes = re.findall(r"^([0-9a-fA-F]+)$", output, re.MULTILINE)
|
||||||
|
if len(sizes) != 1 or int(sizes[0], 16) != len(image):
|
||||||
|
raise RuntimeError("U-Boot filesize differs from the local firmware file")
|
||||||
|
output = cli.command("md.b 0x42000000 0x20", r"^42000000:")
|
||||||
|
dumped = bytearray()
|
||||||
|
for address in ("42000000", "42000010"):
|
||||||
|
rows = re.findall(r"^" + address + r":((?: [0-9a-fA-F]{2}){16})(?:\s|$)", output, re.MULTILINE)
|
||||||
|
if len(rows) != 1:
|
||||||
|
raise RuntimeError("Missing or invalid memory dump at " + address)
|
||||||
|
dumped.extend(bytes.fromhex(rows[0]))
|
||||||
|
if dumped != image[:32]:
|
||||||
|
raise RuntimeError("First 32 bytes in RAM differ from the local firmware file")
|
||||||
|
print("\nTransfer verified: size and first 32 bytes match; no flash write performed.")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("mode", choices=("check", "configure"), default="check", nargs="?")
|
parser.add_argument("mode", choices=("check", "configure", "transfer"), default="check", nargs="?")
|
||||||
parser.add_argument("--port", default="/dev/ttyUSB0")
|
parser.add_argument("--port", default="/dev/ttyUSB0")
|
||||||
|
parser.add_argument("--firmware", type=Path, default=Path(__file__).resolve().parent.parent / "firmware" / "u-boot-sunxi-with-spl.bin")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if args.mode == "transfer" and not args.firmware.is_file():
|
||||||
|
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.
|
||||||
with open("/tmp/uboot-serial.lock", "w") as lock:
|
with open("/tmp/uboot-serial.lock", "w") as lock:
|
||||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
@@ -88,8 +125,14 @@ def main():
|
|||||||
cli.detect()
|
cli.detect()
|
||||||
if args.mode == "check":
|
if args.mode == "check":
|
||||||
return
|
return
|
||||||
|
if args.mode == "transfer":
|
||||||
|
transfer(cli, args.firmware)
|
||||||
|
return
|
||||||
cli.command("version", r"^U-Boot \d")
|
cli.command("version", r"^U-Boot \d")
|
||||||
cli.command("printenv ipaddr serverip ethaddr bootcmd bootargs")
|
cli.command(
|
||||||
|
"printenv ipaddr serverip ethaddr bootcmd bootargs",
|
||||||
|
allow_missing=("ipaddr", "serverip", "ethaddr", "bootcmd", "bootargs"),
|
||||||
|
)
|
||||||
cli.command("setenv ipaddr 192.168.75.100")
|
cli.command("setenv ipaddr 192.168.75.100")
|
||||||
cli.command("printenv ipaddr", r"^ipaddr=192\.168\.75\.100$")
|
cli.command("printenv ipaddr", r"^ipaddr=192\.168\.75\.100$")
|
||||||
cli.command("setenv serverip 192.168.75.247")
|
cli.command("setenv serverip 192.168.75.247")
|
||||||
|
|||||||
مرجع در شماره جدید
مسدود کردن کاربر