[openwrt/openwrt] econet: Add new target SmartFiber XP8421-B

LEDE Commits lede-commits at lists.infradead.org
Thu Sep 11 15:55:01 PDT 2025


hauke pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/ef2785a2d01bb6c8ab42b23f7706983b05be120b

commit ef2785a2d01bb6c8ab42b23f7706983b05be120b
Author: Caleb James DeLisle <cjd at cjdns.fr>
AuthorDate: Mon Sep 1 10:10:56 2025 +0000

    econet: Add new target SmartFiber XP8421-B
    
    The SmartFiber XP8421-B is a fiber modem which is available for $20 online
    and has 512MB of memory, 256MB of SPI NAND flash and 2 USB 2.0 ports in
    addition to ethernet, wifi and XPON.
    
    Because EcoNet is not currently producing evaluation boards, the XP8421-B
    stands in as a convenient, low cost, off-the-shelf, representitive example
    of the capabilities of the EN751221 econet processor. This is also the
    example board that is included in the upstream Linux patchset.
    
    The XP8421-B, and apparently many other devices of this platform, use a
    dual-image layout. I have chosen to reuse this to support dual-boot between
    OpenWRT and the factory firmware. Certain design decisions were made with
    the goal of not overwriting data that is used by the factory OS.
    
    This commit also introduces a utility for switching between OS_A and OS_B
    which are used for OpenWRT and Factory OS respectively.
    
    Flashing instructions (from bootloader):
    
    Build and then locate the squashfs-tclinux.trx image file
    Get the length of that file in hex: printf '%X\n' "$(stat -c%s the-file-squashfs-tclinux.trx)"
    Connect to device with xmodem capability, e.g. picocom --send-cmd lsx -vv -b 115200 /dev/ttyUSB0
    Switch device on and press a key within 3 seconds
    Enter bootloader username and password: telecomadmin nE7jA%5m
    Type: xmdm 80020000 <file length hex>
    Quickly start xmodem and send the file, in picocom that is ctrl+a ctrl+s <paste-the-file-name> enter If the transfer fails to start, wait 30 seconds to a
    minute for the bootloader prompt to return and then try the command again.
    Once the transfer has completed successfully, type the following flash 80000 80020000 <file length hex>
    Type go or simply restart the device to boot into OpenWRT
    
    Signed-off-by: Caleb James DeLisle <cjd at cjdns.fr>
    Link: https://github.com/openwrt/openwrt/pull/19021
    Signed-off-by: Hauke Mehrtens <hauke at hauke-m.de>
---
 target/linux/econet/base-files/sbin/en75_chboot    | 112 +++++++++++++++++++
 .../econet/dts/en751221_smartfiber_xp8421-b.dts    |  82 ++++++++++++++
 target/linux/econet/image/Makefile                 |  25 ++++-
 target/linux/econet/image/tclinux-trx.sh           | 118 +++++++++++++++++++++
 4 files changed, 336 insertions(+), 1 deletion(-)

diff --git a/target/linux/econet/base-files/sbin/en75_chboot b/target/linux/econet/base-files/sbin/en75_chboot
new file mode 100755
index 0000000000..5780190000
--- /dev/null
+++ b/target/linux/econet/base-files/sbin/en75_chboot
@@ -0,0 +1,112 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+part=
+offset_blocks=
+block_size=
+code_openwrt=
+code_factory=
+code_offset=
+
+read_nand() {
+    dd "if=$part" "of=$file" "bs=$block_size" "skip=$offset_blocks" count=1 2>/dev/null
+}
+
+write_nand() {
+    flash_erase -N -q "$part" $((offset_blocks * block_size)) 1
+    dd "if=$file" "of=$part" "bs=$block_size" "seek=$offset_blocks" count=1 2>/dev/null
+}
+
+part_named() {
+    name=$1
+    pn=$(grep "$name" < /proc/mtd | sed 's/:.*//')
+    if [ -z "$pn" ]; then
+        echo "Partition not found: $name"
+        exit 1
+    fi
+    echo "/dev/$pn"
+}
+
+to_hex() {
+    hexdump -v -e '1/1 "%02x"'
+}
+
+from_hex() {
+    sed 's/\([0-9a-fA-F]\{2\}\)/echo -n -e "\\x\1"\n/g' | sh
+}
+
+check() {
+    stored_code=$(dd \
+        "if=$part" \
+        bs=1 \
+        skip=$((offset_blocks * block_size + code_offset)) \
+        count=$((${#code_openwrt} / 2)) \
+            2>/dev/null | to_hex
+    )
+    if [ "$stored_code" = "$code_openwrt" ]; then
+        echo "Current boot flag set to OS A (OpenWrt)"
+    elif [ "$stored_code" = "$code_factory" ]; then
+        echo "Current boot flag set to OS B (Factory)"
+    else
+        echo "Current boot flag unknown: $stored_code"
+    fi
+}
+
+switch() {
+    switch_to=$1
+
+    echo "Switching boot flag to $switch_to"
+    file=$(mktemp)
+    read_nand
+    if [ "$switch_to" = "factory" ]; then
+        echo "$code_factory" | from_hex | \
+            dd "of=$file" bs=1 "seek=$code_offset" conv=notrunc 2>/dev/null
+    elif [ "$switch_to" = "openwrt" ]; then
+        echo "$code_openwrt" | from_hex | \
+            dd "of=$file" bs=1 "seek=$code_offset" conv=notrunc 2>/dev/null
+    else
+        echo "Invalid switch_to: $switch_to"
+        exit 1
+    fi
+    write_nand
+    rm "$file"
+    echo "Flash write complete"
+    check
+}
+
+main() {
+    machine=$(sed -n -e 's/^machine\s\+:\s\+//p' < /proc/cpuinfo)
+    if [ "$machine" = "TP-Link Archer VR1200v (v2)" ]; then
+        # 03fe0000
+        part=$(part_named '"reserve"')
+        offset_blocks=0
+        block_size=$((1024 * 128))
+        code_offset=0
+        code_openwrt=0000000101000002
+        code_factory=0000000101010003
+    elif [ "$machine" = "SmartFiber XP8421-B" ]; then
+        # 0dfc0000
+        part=$(part_named '"reservearea"')
+        offset_blocks=12
+        block_size=$((1024 * 128))
+        code_offset=0
+        code_openwrt=30000000
+        code_factory=31000000
+    else
+        echo "Unsupported machine: $machine"
+        exit 1
+    fi
+
+    if [ "$1" = "factory" ]; then
+        switch factory
+    elif [ "$1" = "openwrt" ]; then
+        switch openwrt
+    else
+        echo "Usage: $0 factory|openwrt  # Change boot flag to Factory OS or OpenWrt"
+        check
+        exit 1
+    fi
+}
+main "$@"
diff --git a/target/linux/econet/dts/en751221_smartfiber_xp8421-b.dts b/target/linux/econet/dts/en751221_smartfiber_xp8421-b.dts
new file mode 100644
index 0000000000..45654492a8
--- /dev/null
+++ b/target/linux/econet/dts/en751221_smartfiber_xp8421-b.dts
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/dts-v1/;
+
+#include "en751221.dtsi"
+
+/ {
+	model = "SmartFiber XP8421-B";
+	compatible = "smartfiber,xp8421-b", "econet,en751221";
+
+	memory at 0 {
+		device_type = "memory";
+		reg = <0x00000000 0x1c000000>;
+	};
+
+	chosen {
+		stdout-path = "/serial at 1fbf0000:115200";
+		linux,usable-memory-range = <0x00020000 0x1bfe0000>;
+	};
+};
+
+&nand {
+	status = "okay";
+	econet,bmt;
+
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition at 0 {
+			label = "bootloader";
+			reg = <0x0 0x40000>;
+			read-only;
+		};
+
+		partition at 40000 {
+			label = "romfile";
+			reg = <0x40000 0x40000>;
+			read-only;
+		};
+
+		partition at 80000 {
+			label = "tclinux";
+			reg = <0x80000 0x1400000>;
+			read-only;
+			econet,enable-remap;
+		};
+
+		/* Nested inside of tclinux */
+		partition at 480000 {
+			label = "rootfs";
+			reg = <0x480000 0xf80000>;
+			linux,rootfs;
+			read-only;
+		};
+
+		partition at 1480000 {
+			label = "tclinux_alt";
+			reg = <0x1480000 0x1400000>;
+		};
+
+		partition at 2880000 {
+			label = "openjdk";
+			reg = <0x2880000 0x2000000>;
+		};
+
+		partition at 4880000 {
+			label = "ubifs";
+			reg = <0x4880000 0x9100000>;
+		};
+
+		partition at d980000 {
+			label = "unknown";
+			reg = <0xd980000 0x4c0000>;
+		};
+
+		partition at de40000 {
+			label = "reservearea";
+			reg = <0xde40000 0x1c0000>;
+		};
+	};
+};
diff --git a/target/linux/econet/image/Makefile b/target/linux/econet/image/Makefile
index 433f6ea679..95bff987cf 100644
--- a/target/linux/econet/image/Makefile
+++ b/target/linux/econet/image/Makefile
@@ -5,6 +5,29 @@ define Target/Description
 	Build firmware images for EcoNet MIPS based boards.
 endef
 
-# Devices will come in a later commit.
+# tclinux-trx is the default format used in the SDK
+define Build/tclinux-trx
+  ./tclinux-trx.sh $@ $(IMAGE_ROOTFS) $(VERSION_DIST)-$(REVISION) > $@.new
+	mv $@.new $@
+endef
+
+# tclinux bootloader requires LZMA, BUT only provides 7.5MB of space
+# to decompress into. So we use vmlinuz and decompress twice.
+define Device/Default
+  DEVICE_DTS_DIR := ../dts
+  KERNEL_SIZE := 7480k
+  KERNEL_NAME := vmlinuz.bin
+  KERNEL_LOADADDR := 0x80020000
+  KERNEL := kernel-bin | append-dtb
+endef
+
+define Device/smartfiber_xp8421-b
+  DEVICE_VENDOR := SmartFiber
+  DEVICE_MODEL := XP8421-B
+  DEVICE_DTS := en751221_smartfiber_xp8421-b
+  IMAGES := tclinux.trx
+  IMAGE/tclinux.trx := append-kernel | lzma | tclinux-trx
+endef
+TARGET_DEVICES += smartfiber_xp8421-b
 
 $(eval $(call BuildImage))
diff --git a/target/linux/econet/image/tclinux-trx.sh b/target/linux/econet/image/tclinux-trx.sh
new file mode 100755
index 0000000000..90a88d0543
--- /dev/null
+++ b/target/linux/econet/image/tclinux-trx.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# This is not necessary, but it makes finding the rootfs easier.
+PAD_ROOTFS_OFFSET_TO=4194304
+
+# Constant
+HDRLEN=256
+
+die() {
+    echo "$1" >&2
+    exit 1
+}
+
+[ $# -eq 3 ] || die "SYNTAX: $0 <kernel lzma> <rootfs squashfs> <version string>"
+kernel=$1
+rootfs=$2
+version=$3
+which zytrx >/dev/null || die "zytrx not found in PATH $PATH"
+[ -f "$kernel" ] || die "Kernel file not found: $kernel"
+[ -f "$rootfs" ] || die "Rootfs file not found: $rootfs"
+[ "$(echo "$version" | wc -c)" -lt 32 ] || die "Version string too long: $version"
+
+kernel_len=$(stat -c '%s' "$kernel")
+header_plus_kernel_len=$(($HDRLEN + $kernel_len))
+rootfs_len=$(stat -c '%s' "$rootfs")
+
+if [ "$PAD_ROOTFS_OFFSET_TO" -gt "$header_plus_kernel_len" ]; then
+    padding_len=$(($PAD_ROOTFS_OFFSET_TO - $header_plus_kernel_len))
+else
+    padding_len=0
+fi
+
+echo "padding_len: $padding_len" >&2
+
+padded_rootfs_len=$(($padding_len + $rootfs_len))
+
+echo "padded_rootfs_len: $padded_rootfs_len" >&2
+
+total_len=$(($header_plus_kernel_len + $padded_rootfs_len))
+
+echo "total_len: $total_len" >&2
+
+padding() {
+    head -c $padding_len /dev/zero | tr '\0' '\377'
+}
+
+to_hex() {
+    hexdump -v -e '1/1 "%02x"'
+}
+
+from_hex() {
+    perl -pe 's/\s+//g; s/(..)/chr(hex($1))/ge'
+}
+
+trx_crc32() {
+    tmpfile=$(mktemp)
+    outtmpfile=$(mktemp)
+    cat "$kernel" > "$tmpfile"
+    padding >> "$tmpfile"
+    cat "$rootfs" >> "$tmpfile"
+    # We just need a CRC-32/JAMCRC of the concatnated files
+    # There's no readily available tool for this, but zytrx does create one when
+    # creating their TRX header, so we just use that.
+    zytrx \
+        -B NR7101 \
+        -v x \
+        -i "$tmpfile" \
+        -o "$outtmpfile" >/dev/null
+    dd if="$outtmpfile" bs=4 count=1 skip=3 | to_hex
+    rm "$tmpfile" "$outtmpfile" >/dev/null
+}
+
+tclinux_trx_hdr() {
+    # TRX header magic
+    printf '2RDH' | to_hex
+
+    # Length of the header
+    printf '%08x\n' "$HDRLEN"
+
+    # Length of header + content
+    printf '%08x\n' "$total_len"
+
+    # crc32 of the content
+    trx_crc32
+
+    # version
+    echo "$version" | to_hex
+    head -c "$((32 - $(echo "$version" | wc -c)))" /dev/zero | to_hex
+
+    # customer version
+    head -c 32 /dev/zero | to_hex
+
+    # kernel length
+    printf '%08x\n' "$kernel_len"
+
+    # rootfs length
+    printf '%08x\n' "$padded_rootfs_len"
+
+    # romfile length (0)
+    printf '00000000\n'
+
+    # "model" (32 bytes of zeros)
+    head -c 32 /dev/zero | to_hex
+
+    # Load address (CONFIG_ZBOOT_LOAD_ADDRESS)
+    printf '80020000\n'
+
+    # "reserved" 128 bytes of zeros
+    head -c 128 /dev/zero | to_hex
+}
+
+tclinux_trx_hdr | from_hex
+cat "$kernel"
+padding
+cat "$rootfs"




More information about the lede-commits mailing list