مقایسه کامیتها
2
کامیتها
| مولف | SHA1 | تاریخ | |
|---|---|---|---|
|
|
c9d72c6319 | ||
|
|
793504fc78 |
@@ -0,0 +1,65 @@
|
||||
name: flash-aminpa-V1.5
|
||||
# Production flash for Amin Pardaz Asia device v1.5
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: aminpa-serial-flash
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
flash-aminpa:
|
||||
name: Production flash - Amin Pardaz Asia v1.5
|
||||
runs-on: serial-runner
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate all firmware files before erasing flash
|
||||
run: |
|
||||
/usr/bin/python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
required = {
|
||||
"u-boot-sunxi-with-spl.bin": 32,
|
||||
"sun8i-v3s-licheepi-zero-dock.dtb": 32,
|
||||
"zImage": 64,
|
||||
"rootfs.squashfs": 64,
|
||||
}
|
||||
for filename, minimum in required.items():
|
||||
path = Path("firmware") / filename
|
||||
if not path.is_file() or path.stat().st_size < minimum:
|
||||
raise SystemExit(f"Missing or invalid firmware: {path}")
|
||||
print(f"Found {path}: {path.stat().st_size} bytes")
|
||||
PY
|
||||
|
||||
- name: Check current U-Boot CLI
|
||||
run: /usr/bin/python3 scripts/serial_command.py check
|
||||
|
||||
- name: Configure and verify network
|
||||
run: /usr/bin/python3 scripts/serial_command.py configure
|
||||
|
||||
- name: Check all firmware sizes against detected flash layout
|
||||
run: /usr/bin/python3 scripts/serial_command.py preflight
|
||||
|
||||
- name: Erase detected SPI flash and verify sample
|
||||
run: /usr/bin/python3 scripts/serial_command.py erase
|
||||
|
||||
- name: Transfer, write and verify U-Boot
|
||||
run: /usr/bin/python3 scripts/serial_command.py transfer
|
||||
|
||||
- name: Transfer, write and verify DTB
|
||||
run: /usr/bin/python3 scripts/serial_command.py transfer-dtb
|
||||
|
||||
- name: Transfer, write and verify Kernel
|
||||
run: /usr/bin/python3 scripts/serial_command.py transfer-kernel
|
||||
|
||||
- name: Transfer, write and verify RootFS
|
||||
run: /usr/bin/python3 scripts/serial_command.py transfer-rootfs
|
||||
|
||||
- name: Inspect boot configuration
|
||||
run: /usr/bin/python3 scripts/serial_command.py boot-config
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Erase Entire 32 MiB SPI Flash
|
||||
name: Detect and Erase Entire SPI Flash
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -2,11 +2,6 @@ name: Transfer, Write and Verify RootFS
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rootfs_size:
|
||||
description: 'Actual RootFS partition size in bytes (decimal or 0x-prefixed hex)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
rootfs-transfer:
|
||||
@@ -16,8 +11,6 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Transfer RootFS, write SPI flash and verify read-back
|
||||
env:
|
||||
ROOTFS_PARTITION_SIZE: ${{ inputs.rootfs_size }}
|
||||
- name: Detect flash, transfer RootFS and verify read-back
|
||||
run: |
|
||||
/usr/bin/python3 scripts/serial_command.py transfer-rootfs --rootfs-size "$ROOTFS_PARTITION_SIZE"
|
||||
/usr/bin/python3 scripts/serial_command.py transfer-rootfs
|
||||
|
||||
+28
-5
@@ -1,6 +1,29 @@
|
||||
Place firmware files here and serve this directory as the existing TFTP
|
||||
server root. Checkout alone does not publish files over TFTP.
|
||||
|
||||
The separate `Erase Entire 32 MiB SPI Flash` workflow erases offsets
|
||||
0x0 through 0x1ffffff, including all firmware and any saved environment.
|
||||
It requires a successful 32 MiB flash probe, checks the erase/read command
|
||||
results, and verifies that the first 256 bytes are FF. This sample does
|
||||
not verify every byte of the flash. No automatic retries are performed.
|
||||
Every transfer starts with sf probe 0:0. The reported total capacity must
|
||||
be exactly 16 or 32 MiB; an unknown or ambiguous result stops execution.
|
||||
The selected offsets and maximum sizes (hex bytes) are:
|
||||
|
||||
| File | 16 MiB offset / maximum | 32 MiB offset / maximum |
|
||||
| --- | --- | --- |
|
||||
| u-boot-sunxi-with-spl.bin | 0 / 0x100000 | 0 / 0x200000 |
|
||||
| sun8i-v3s-licheepi-zero-dock.dtb | 0x100000 / 0x10000 | 0x200000 / 0x110000 |
|
||||
| zImage | 0x110000 / 0x500000 | 0x310000 / 0x500000 |
|
||||
| rootfs.squashfs | 0x610000 / 0x900000 | 0x810000 / 0x17f0000 |
|
||||
|
||||
The 32 MiB layout allocates the remainder of flash to RootFS. These are
|
||||
selected project layouts, not partition tables read from the chip. DTB
|
||||
partitions and bootcmd must match the selected layout. A smaller actual
|
||||
RootFS region can be specified with --rootfs-size; larger overrides fail.
|
||||
|
||||
Transfers reset and verify filesize=0 before TFTP, require successful
|
||||
transfer output and status, and compare the hexadecimal filesize with the
|
||||
local file and transfer count. First 32 bytes (U-Boot/DTB) or 64 bytes
|
||||
(Kernel/RootFS) are compared with the local file. Writes are read back at
|
||||
0x42800000 and compared in full. No automatic erase or retry is performed
|
||||
in transfer workflows. Flash must be erased before programming as needed.
|
||||
|
||||
The separate erase workflow detects capacity and erases the entire chip,
|
||||
including firmware and saved environment. It verifies only the first 256
|
||||
bytes as FF, not every byte. No workflow automatically reboots the board.
|
||||
|
||||
@@ -82,16 +82,37 @@ class UBoot:
|
||||
return output
|
||||
|
||||
|
||||
LAYOUTS = {
|
||||
16: {"uboot": (0, 0x100000), "dtb": (0x100000, 0x10000),
|
||||
"kernel": (0x110000, 0x500000), "rootfs": (0x610000, 0x900000)},
|
||||
32: {"uboot": (0, 0x200000), "dtb": (0x200000, 0x110000),
|
||||
"kernel": (0x310000, 0x500000), "rootfs": (0x810000, 0x17f0000)},
|
||||
}
|
||||
|
||||
|
||||
def detect_flash(cli):
|
||||
output = cli.command("sf probe 0:0", r"^SF: Detected .+", timeout=20)
|
||||
capacities = re.findall(r"^SF: Detected .*\btotal (\d+) MiB\s*$", output, re.MULTILINE)
|
||||
if len(capacities) != 1 or int(capacities[0]) not in LAYOUTS:
|
||||
raise RuntimeError("Unsupported or ambiguous flash capacity; expected 16 or 32 MiB")
|
||||
capacity = int(capacities[0])
|
||||
print(f"\nDetected {capacity} MiB ({capacity * 8} Mbit); selecting matching flash layout.")
|
||||
return capacity
|
||||
|
||||
|
||||
def transfer(cli, firmware, *, dtb=False, kernel=False, rootfs=False, rootfs_size=None):
|
||||
capacity = detect_flash(cli)
|
||||
part = "rootfs" if rootfs else "kernel" if kernel else "dtb" if dtb else "uboot"
|
||||
offset, limit = LAYOUTS[capacity][part]
|
||||
address = 0x41000000 if (dtb or rootfs) else 0x42000000
|
||||
offset = 0x310000 if kernel else (0x200000 if dtb else 0)
|
||||
limit = 0x500000 if kernel else (0x110000 if dtb else 0x200000)
|
||||
dump_size = 64 if kernel else 32
|
||||
filename = "zImage" if kernel else ("sun8i-v3s-licheepi-zero-dock.dtb" if dtb else "u-boot-sunxi-with-spl.bin")
|
||||
if rootfs:
|
||||
if rootfs_size is None or rootfs_size < 64:
|
||||
raise RuntimeError("Actual RootFS partition size is required (at least 64 bytes)")
|
||||
offset, limit, dump_size, filename = 0x810000, rootfs_size, 64, "rootfs.squashfs"
|
||||
dump_size = 64 if (kernel or rootfs) else 32
|
||||
filename = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb",
|
||||
"kernel": "zImage", "rootfs": "rootfs.squashfs"}[part]
|
||||
if rootfs_size is not None and rootfs:
|
||||
if not 64 <= rootfs_size <= limit:
|
||||
raise RuntimeError("RootFS size override exceeds the selected partition or is invalid")
|
||||
limit = rootfs_size
|
||||
print(f"Selected {part}: flash offset {offset:#x}, maximum size {limit:#x}")
|
||||
image = firmware.read_bytes()
|
||||
if not dump_size <= len(image) <= limit:
|
||||
raise RuntimeError(f"Firmware size must be between {dump_size} and {limit:#x} bytes")
|
||||
@@ -122,7 +143,6 @@ def transfer(cli, firmware, *, dtb=False, kernel=False, rootfs=False, rootfs_siz
|
||||
if dumped != image[:dump_size]:
|
||||
raise RuntimeError(f"First {dump_size} bytes in RAM differ from the local firmware file")
|
||||
print(f"\nTransfer verified: size and first {dump_size} bytes match.")
|
||||
cli.command("sf probe 0:0", r"^SF: Detected .+", timeout=20)
|
||||
cli.command(
|
||||
f"sf write {address:#x} {offset:#x} ${{filesize}}",
|
||||
rf"^SF: {len(image)} bytes @ {offset:#x} Written: OK\s*$", timeout=120,
|
||||
@@ -142,10 +162,11 @@ def transfer(cli, firmware, *, dtb=False, kernel=False, rootfs=False, rootfs_siz
|
||||
|
||||
|
||||
def erase_flash(cli):
|
||||
cli.command("sf probe 0:0", r"^SF: Detected .+total 32 MiB\s*$", timeout=20)
|
||||
capacity = detect_flash(cli)
|
||||
length = capacity * 1024 * 1024
|
||||
cli.command(
|
||||
"sf erase 0x0 0x2000000",
|
||||
r"^SF: 33554432 bytes @ 0x0 Erased: OK\s*$", timeout=600,
|
||||
f"sf erase 0x0 {length:#x}",
|
||||
rf"^SF: {length} bytes @ 0x0 Erased: OK\s*$", timeout=600,
|
||||
)
|
||||
cli.command(
|
||||
"sf read 0x42800000 0x0 0x100",
|
||||
@@ -178,14 +199,12 @@ def boot_config(cli, confirmed=False):
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs", "erase", "boot-config"), default="check", nargs="?")
|
||||
parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs", "erase", "boot-config", "preflight"), default="check", nargs="?")
|
||||
parser.add_argument("--port", default="/dev/ttyUSB0")
|
||||
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("--confirm-squashfs-mtdblock3", action="store_true", help="Confirm actual RootFS is SquashFS on mtdblock3 and set bootargs")
|
||||
args = parser.parse_args()
|
||||
if args.mode == "transfer-rootfs" and (args.rootfs_size is None or args.rootfs_size < 64):
|
||||
parser.error("--rootfs-size must specify the actual RootFS partition size (at least 64 bytes)")
|
||||
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")
|
||||
args.firmware = Path(__file__).resolve().parent.parent / "firmware" / filename
|
||||
@@ -199,6 +218,18 @@ def main():
|
||||
cli.detect()
|
||||
if args.mode == "check":
|
||||
return
|
||||
if args.mode == "preflight":
|
||||
capacity = detect_flash(cli)
|
||||
files = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb",
|
||||
"kernel": "zImage", "rootfs": "rootfs.squashfs"}
|
||||
for part, filename in files.items():
|
||||
path = Path(__file__).resolve().parent.parent / "firmware" / filename
|
||||
minimum = 64 if part in ("kernel", "rootfs") else 32
|
||||
limit = LAYOUTS[capacity][part][1]
|
||||
if not path.is_file() or not minimum <= path.stat().st_size <= limit:
|
||||
raise RuntimeError(f"Firmware missing or outside selected partition size: {path} (maximum {limit:#x})")
|
||||
print("All firmware sizes fit the detected flash layout.")
|
||||
return
|
||||
if args.mode == "erase":
|
||||
erase_flash(cli)
|
||||
return
|
||||
|
||||
مرجع در شماره جدید
مسدود کردن کاربر