مقایسه کامیت‌‌ها

...
2 کامیت‌ها
مولف SHA1 پیام تاریخ
nearology c9d72c6319 اضافه شدن ورک فلو برد تستی
flash-aminpa-V1.5 / Production flash - Amin Pardaz Asia v1.5 (push) Successful in 7m15s
2026-11-09 21:07:37 +03:30
nearology 793504fc78 اضافه شدن قابلیت تشخیص حافظه 2026-11-09 21:05:20 +03:30
5فایلهای تغییر یافته به همراه142 افزوده شده و 30 حذف شده
@@ -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: on:
workflow_dispatch: workflow_dispatch:
@@ -2,11 +2,6 @@ name: Transfer, Write and Verify RootFS
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
rootfs_size:
description: 'Actual RootFS partition size in bytes (decimal or 0x-prefixed hex)'
required: true
type: string
jobs: jobs:
rootfs-transfer: rootfs-transfer:
@@ -16,8 +11,6 @@ jobs:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Transfer RootFS, write SPI flash and verify read-back - name: Detect flash, transfer RootFS and verify read-back
env:
ROOTFS_PARTITION_SIZE: ${{ inputs.rootfs_size }}
run: | 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 Every transfer starts with sf probe 0:0. The reported total capacity must
0x0 through 0x1ffffff, including all firmware and any saved environment. be exactly 16 or 32 MiB; an unknown or ambiguous result stops execution.
It requires a successful 32 MiB flash probe, checks the erase/read command The selected offsets and maximum sizes (hex bytes) are:
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. | 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.
+46 -15
مشاهده پرونده
@@ -82,16 +82,37 @@ class UBoot:
return output 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): 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 address = 0x41000000 if (dtb or rootfs) else 0x42000000
offset = 0x310000 if kernel else (0x200000 if dtb else 0) dump_size = 64 if (kernel or rootfs) else 32
limit = 0x500000 if kernel else (0x110000 if dtb else 0x200000) filename = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb",
dump_size = 64 if kernel else 32 "kernel": "zImage", "rootfs": "rootfs.squashfs"}[part]
filename = "zImage" if kernel else ("sun8i-v3s-licheepi-zero-dock.dtb" if dtb else "u-boot-sunxi-with-spl.bin") if rootfs_size is not None and rootfs:
if rootfs: if not 64 <= rootfs_size <= limit:
if rootfs_size is None or rootfs_size < 64: raise RuntimeError("RootFS size override exceeds the selected partition or is invalid")
raise RuntimeError("Actual RootFS partition size is required (at least 64 bytes)") limit = rootfs_size
offset, limit, dump_size, filename = 0x810000, rootfs_size, 64, "rootfs.squashfs" print(f"Selected {part}: flash offset {offset:#x}, maximum size {limit:#x}")
image = firmware.read_bytes() image = firmware.read_bytes()
if not dump_size <= len(image) <= limit: if not dump_size <= len(image) <= limit:
raise RuntimeError(f"Firmware size must be between {dump_size} and {limit:#x} bytes") 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]: if dumped != image[:dump_size]:
raise RuntimeError(f"First {dump_size} bytes in RAM differ from the local firmware file") 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.") print(f"\nTransfer verified: size and first {dump_size} bytes match.")
cli.command("sf probe 0:0", r"^SF: Detected .+", timeout=20)
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: {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): 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( cli.command(
"sf erase 0x0 0x2000000", f"sf erase 0x0 {length:#x}",
r"^SF: 33554432 bytes @ 0x0 Erased: OK\s*$", timeout=600, rf"^SF: {length} bytes @ 0x0 Erased: OK\s*$", timeout=600,
) )
cli.command( cli.command(
"sf read 0x42800000 0x0 0x100", "sf read 0x42800000 0x0 0x100",
@@ -178,14 +199,12 @@ 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"), 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("--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")
args = parser.parse_args() 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: 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().parent.parent / "firmware" / filename
@@ -199,6 +218,18 @@ def main():
cli.detect() cli.detect()
if args.mode == "check": if args.mode == "check":
return 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": if args.mode == "erase":
erase_flash(cli) erase_flash(cli)
return return