From c101fc5b9d5e0aded57128912e6cebfaf6987a37 Mon Sep 17 00:00:00 2001 From: Tomaz Zaman Date: Mon, 10 Aug 2026 23:08:26 +0200 Subject: [PATCH 27/59] layerscape: complete + robust GPT, per-device geometry Addresses review findings S1/S5/W3/W5. - Backup GPT (S1): the flashed image carries only the primary GPT (it is trimmed to the payload), so partition tools saw a half-corrupt table and could 'repair' it to a 128-entry array crossing into the 4 KiB-32 MiB firmware region - a brick vector. The GPT writer is factored into mono_gpt.py (primary + backup modes); the boot partition now ships a 33-sector backup blob that first boot lays down at the device end. sgdisk --verify: no problems. - Footgun (S1/S5): dropped sgdisk from the image; a real flash script now ships with each release instead of a two-step dd in a commit message. - Geometry (W5): eMMC size and rootfs partition are per-device vars (MONO_EMMC_SECTORS, MONO_ROOTFS_PART), not baked into the generator. - First boot (W3/W7): the expand script's stale '5 MiB entry array' comment is corrected and its resize result is checked and retried. Co-Authored-By: Claude Fable 5 --- .../10-mono-gateway-expand-rootfs | 55 ++++++-- target/linux/layerscape/image/armv8_64b.mk | 9 +- .../layerscape/image/gen_mono_emmc_img.sh | 121 ++++-------------- target/linux/layerscape/image/mono_gpt.py | 81 ++++++++++++ 4 files changed, 155 insertions(+), 111 deletions(-) create mode 100644 target/linux/layerscape/image/mono_gpt.py diff --git a/target/linux/layerscape/base-files/etc/uci-defaults/10-mono-gateway-expand-rootfs b/target/linux/layerscape/base-files/etc/uci-defaults/10-mono-gateway-expand-rootfs index f6d9fb4933..814defb48b 100644 --- a/target/linux/layerscape/base-files/etc/uci-defaults/10-mono-gateway-expand-rootfs +++ b/target/linux/layerscape/base-files/etc/uci-defaults/10-mono-gateway-expand-rootfs @@ -1,17 +1,50 @@ #!/bin/sh -# The eMMC image ships a 1 GB ext4 rootfs inside a partition already -# sized to the full eMMC (the GPT is never rewritten on-device because -# the entry array lives at 5 MiB, clear of the raw boot firmware, and -# standard partition tools would relocate it). Grow the filesystem to -# the partition once, online, on first boot. +# First-boot disk fixups for the Mono Gateway. Two jobs: +# 1. Grow the rootfs to fill its partition. The image ships a small ext4 +# inside a partition already sized to the full eMMC (the on-device GPT +# is never rewritten by an upgrade, and its entry array sits in the +# first 4 KiB clear of the boot firmware; see mono_gpt.py). +# 2. Write the backup GPT to the end of the device. The flashed image is +# trimmed and carries only the primary GPT, so the secondary copy is +# laid down here from the blob shipped in the boot partition. This makes +# the on-disk table complete so partition tools do not "repair" it. +# uci-defaults run once and self-delete on exit 0; both steps are idempotent +# so a re-run after sysupgrade is harmless. . /lib/functions.sh case "$(board_name)" in -mono,gateway-dk|mono,gateway-dk-sdboot) - root_dev=$(readlink -f /dev/root 2>/dev/null) - [ "$root_dev" = "/dev/mmcblk0p2" ] || root_dev=/dev/mmcblk0p2 - resize2fs "$root_dev" 2>&1 | logger -t expand-rootfs - ;; +mono,gateway-dk|mono,gateway-dk-sdboot) ;; +*) exit 0 ;; esac -exit 0 +disk=/dev/mmcblk0 +rc=0 + +# 1. grow rootfs +root_dev=$(readlink -f /dev/root 2>/dev/null) +[ -b "$root_dev" ] || root_dev=/dev/mmcblk0p2 +if ! resize2fs "$root_dev" 2>&1 | logger -t mono-firstboot; then + logger -t mono-firstboot "resize2fs failed on $root_dev" + rc=1 +fi + +# 2. lay down the backup GPT from the blob in the boot partition +if [ -b /dev/mmcblk0p1 ]; then + mkdir -p /tmp/bootp + if mount -t ext4 -o ro /dev/mmcblk0p1 /tmp/bootp 2>/dev/null; then + if [ -f /tmp/bootp/boot/backup-gpt.bin ]; then + sectors=$(blockdev --getsz "$disk" 2>/dev/null) + if [ -n "$sectors" ]; then + dd if=/tmp/bootp/boot/backup-gpt.bin of="$disk" \ + bs=512 seek=$((sectors - 33)) conv=fsync 2>/dev/null \ + && logger -t mono-firstboot "backup GPT written at sector $((sectors - 33))" \ + || { logger -t mono-firstboot "backup GPT write failed"; rc=1; } + fi + fi + umount /tmp/bootp 2>/dev/null + fi + rmdir /tmp/bootp 2>/dev/null +fi + +# Non-zero keeps this script for a retry next boot. +exit $rc diff --git a/target/linux/layerscape/image/armv8_64b.mk b/target/linux/layerscape/image/armv8_64b.mk index 7998633562..04b0da8372 100644 --- a/target/linux/layerscape/image/armv8_64b.mk +++ b/target/linux/layerscape/image/armv8_64b.mk @@ -415,6 +415,10 @@ define Build/mono-bootfs $(CP) $(DEVICE_DTS_DIR)/$(DEVICE_DTS).dtb $@.bootdir/boot/ printf 'label OpenWrt\n\tkernel /boot/Image.gz\n\tfdt /boot/%s.dtb\n\tappend root=/dev/mmcblk0p2 rootwait console=ttyS0,115200 earlycon=uart8250,mmio,0x21c0500\n' \ "$(DEVICE_DTS)" > $@.bootdir/boot/extlinux/extlinux.conf + # Backup GPT (33-sector tail) travels in the boot partition; first boot + # dd's it to the device end so the on-disk table is complete. + python3 mono_gpt.py backup $@.bootdir/boot/backup-gpt.bin \ + $(MONO_EMMC_SECTORS) $(MONO_BOOTFS_SIZE) $(MONO_ROOTFS_PART) truncate -s $(MONO_BOOTFS_SIZE)M $@.bootfs $(STAGING_DIR_HOST)/bin/mkfs.ext4 -F -L boot -d $@.bootdir $@.bootfs endef @@ -422,7 +426,7 @@ endef define Build/mono-emmc-img rm -f $@ ./gen_mono_emmc_img.sh $@ $@.bootfs $(IMAGE_ROOTFS) \ - $(MONO_BOOTFS_SIZE) $(MONO_ROOTFS_PART) + $(MONO_BOOTFS_SIZE) $(MONO_ROOTFS_PART) $(MONO_EMMC_SECTORS) endef # Same resize-metadata prep as the eMMC image (make_ext4fs output @@ -459,12 +463,13 @@ define Device/mono_gateway-dk block-mount kmod-usb-storage-uas kmod-fs-exfat kmod-fs-ntfs3 \ kmod-fs-vfat smartmontools usbutils pciutils i2c-tools \ tmux vim-full curl rsync jq less bind-dig openssh-sftp-server \ - sgdisk file ip-full resize2fs mono-update-check + usign ca-bundle file ip-full resize2fs mono-update-check KERNEL_NAME := Image KERNEL := kernel-bin | gzip FILESYSTEMS := ext4 MONO_BOOTFS_SIZE := 64 MONO_ROOTFS_PART := 30208 + MONO_EMMC_SECTORS := 62160896 SUPPORTED_DEVICES := mono,gateway-dk mono,gateway-dk-sdboot IMAGES := emmc.img.gz sysupgrade.bin IMAGE/emmc.img.gz := mono-bootfs | mono-emmc-img | gzip | append-metadata diff --git a/target/linux/layerscape/image/gen_mono_emmc_img.sh b/target/linux/layerscape/image/gen_mono_emmc_img.sh index 65c2b4a587..233d9692aa 100755 --- a/target/linux/layerscape/image/gen_mono_emmc_img.sh +++ b/target/linux/layerscape/image/gen_mono_emmc_img.sh @@ -1,34 +1,26 @@ #!/usr/bin/env bash # SPDX-License-Identifier: GPL-2.0-only # -# Mono Gateway eMMC image: -# - complete GPT within the first 4 KiB: protective MBR (LBA0), -# header (LBA1), 8-entry partition array (LBA2-3). The firmware -# update tool owns EVERYTHING from 4 KiB to 32 MiB and rewrites it -# wholesale, so no GPT structure may live there. 8 entries instead -# of the customary 128 is a header-declared value the kernel and -# U-Boot honor (ptgen cannot emit small arrays, hence the inline -# writer below). -# - partition 1 (boot, ext4: /boot/extlinux + Image.gz + dtb) at 32 MiB -# - partition 2 (rootfs) right after, partition sized to the full -# eMMC; the filesystem inside is smaller and grows on first boot +# Mono Gateway eMMC image. Layout (see mono_gpt.py for the GPT rationale): +# 0-4 KiB complete GPT (8-entry array), clear of the firmware region +# 4 KiB-32 MiB boot firmware, owned by a separate update tool - untouched +# 32 MiB partition 1: boot (ext4: /boot/extlinux + Image.gz + dtb) +# +bootfs partition 2: rootfs, partition sized to the eMMC; the ext4 +# inside is smaller and grows on first boot # set -ex -[ $# -eq 5 ] || { - echo "SYNTAX: $0 " +[ $# -eq 6 ] || { + echo "SYNTAX: $0 " exit 1 } - -OUTPUT="$1" -BOOTFS="$2" -ROOTFS="$3" -BOOTFSSIZE="$4" -ROOTFSPARTSIZE="$5" - -# make_ext4fs output cannot be grown online (its reserved-GDT layout is -# rejected by the kernel resizer: "reserved block not at offset"). -# e2fsck plus ONE offline grow on the host rebuilds the resize metadata; -# after that the first-boot online resize works. Verified via loop mount. +OUTPUT="$1"; BOOTFS="$2"; ROOTFS="$3" +BOOTFSSIZE="$4"; ROOTFSPARTSIZE="$5"; DISKSECTORS="$6" +HERE="$(dirname "$0")" + +# make_ext4fs output cannot be grown online (the kernel resizer rejects its +# reserved-GDT layout: "reserved block not at offset"). e2fsck plus ONE +# offline grow on the host rebuilds the resize metadata; first-boot online +# resize then works. Verified via loop mount. cp "$ROOTFS" "$OUTPUT.rootfs" e2fsck -fy "$OUTPUT.rootfs" || [ $? -le 2 ] truncate -s 384M "$OUTPUT.rootfs" @@ -36,84 +28,17 @@ resize2fs "$OUTPUT.rootfs" ROOTFS="$OUTPUT.rootfs" rm -f "$OUTPUT" -python3 - "$OUTPUT" "$BOOTFSSIZE" "$ROOTFSPARTSIZE" <<'PYEOF' -import struct, sys, uuid, zlib - -out, bootmb, rootmb = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]) -SEC = 512 -ENTRIES = 8 -ENTRY_SZ = 128 -# DK eMMC: 31080448 KiB. Only informational fields derive from this; -# partitions themselves fit any device at least this large. -DISK_LAST = 31080448 * 2 - 1 - -def guid(s): - return uuid.UUID(s).bytes_le - -LINUX_FS = guid("0FC63DAF-8483-4772-8E79-3D69D8477DE4") -# Fixed GUIDs for reproducible images -DISK_GUID = guid("6D6F6E6F-1046-4000-8000-4D6F6E6F0000") -PART_GUIDS = [guid("6D6F6E6F-1046-4000-8000-4D6F6E6F0001"), - guid("6D6F6E6F-1046-4000-8000-4D6F6E6F0002")] - -boot_first = 32 * 2048 # 32 MiB, in sectors -boot_last = boot_first + bootmb * 2048 - 1 -root_first = boot_last + 1 -root_last = root_first + rootmb * 2048 - 1 - -def entry(type_guid, part_guid, first, last, name): - e = type_guid + part_guid + struct.pack("= end of the entry array (LBA 4). Use the 32 MiB -# boundary so the table itself documents the firmware reserve. -def header2(entries_crc): - h = bytearray(header(entries_crc)) - struct.pack_into(" protective MBR + header (LBA1) + array (LBA2-3), for the +# head of the flashable image. +# backup -> array + header tail (33 sectors) written at LBA N-33 of the +# real device on first boot; makes the on-disk table complete +# so partition tools do not see a half-corrupt GPT and try to +# "repair" it back to a 128-entry table across the 4 KiB line. +import struct, sys, uuid, zlib + +def guid(s): return uuid.UUID(s).bytes_le + +LINUX_FS = guid("0FC63DAF-8483-4772-8E79-3D69D8477DE4") +DISK_GUID = guid("6D6F6E6F-1046-4000-8000-4D6F6E6F0000") +PART_GUID = [guid("6D6F6E6F-1046-4000-8000-4D6F6E6F0001"), + guid("6D6F6E6F-1046-4000-8000-4D6F6E6F0002")] +SEC, ENTRIES, ENTRY_SZ = 512, 8, 128 + +def build(disk_sectors, boot_mb, root_part_mb): + boot_first = 32 * 2048 + boot_last = boot_first + boot_mb * 2048 - 1 + root_first = boot_last + 1 + root_last = root_first + root_part_mb * 2048 - 1 + if root_last > disk_sectors - 34: + sys.exit("mono_gpt: rootfs partition (%d MiB) exceeds device (%d sectors)" + % (root_part_mb, disk_sectors)) + + def entry(pg, first, last, name): + return (LINUX_FS + pg + struct.pack("