]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
econet: Add new target SmartFiber XP8421-B
authorCaleb James DeLisle <cjd@cjdns.fr>
Mon, 1 Sep 2025 10:10:56 +0000 (10:10 +0000)
committerHauke Mehrtens <hauke@hauke-m.de>
Thu, 11 Sep 2025 22:51:58 +0000 (00:51 +0200)
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@cjdns.fr>
Link: https://github.com/openwrt/openwrt/pull/19021
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
target/linux/econet/base-files/sbin/en75_chboot [new file with mode: 0755]
target/linux/econet/dts/en751221_smartfiber_xp8421-b.dts [new file with mode: 0644]
target/linux/econet/image/Makefile
target/linux/econet/image/tclinux-trx.sh [new file with mode: 0755]

diff --git a/target/linux/econet/base-files/sbin/en75_chboot b/target/linux/econet/base-files/sbin/en75_chboot
new file mode 100755 (executable)
index 0000000..5780190
--- /dev/null
@@ -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 (file)
index 0000000..4565449
--- /dev/null
@@ -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@0 {
+               device_type = "memory";
+               reg = <0x00000000 0x1c000000>;
+       };
+
+       chosen {
+               stdout-path = "/serial@1fbf0000:115200";
+               linux,usable-memory-range = <0x00020000 0x1bfe0000>;
+       };
+};
+
+&nand {
+       status = "okay";
+       econet,bmt;
+
+       partitions {
+               compatible = "fixed-partitions";
+               #address-cells = <1>;
+               #size-cells = <1>;
+
+               partition@0 {
+                       label = "bootloader";
+                       reg = <0x0 0x40000>;
+                       read-only;
+               };
+
+               partition@40000 {
+                       label = "romfile";
+                       reg = <0x40000 0x40000>;
+                       read-only;
+               };
+
+               partition@80000 {
+                       label = "tclinux";
+                       reg = <0x80000 0x1400000>;
+                       read-only;
+                       econet,enable-remap;
+               };
+
+               /* Nested inside of tclinux */
+               partition@480000 {
+                       label = "rootfs";
+                       reg = <0x480000 0xf80000>;
+                       linux,rootfs;
+                       read-only;
+               };
+
+               partition@1480000 {
+                       label = "tclinux_alt";
+                       reg = <0x1480000 0x1400000>;
+               };
+
+               partition@2880000 {
+                       label = "openjdk";
+                       reg = <0x2880000 0x2000000>;
+               };
+
+               partition@4880000 {
+                       label = "ubifs";
+                       reg = <0x4880000 0x9100000>;
+               };
+
+               partition@d980000 {
+                       label = "unknown";
+                       reg = <0xd980000 0x4c0000>;
+               };
+
+               partition@de40000 {
+                       label = "reservearea";
+                       reg = <0xde40000 0x1c0000>;
+               };
+       };
+};
index 433f6ea679196ff8890bc8054fb99eb6439cff88..95bff987cf6689b292a6dc30ebce91d19f3967aa 100644 (file)
@@ -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 (executable)
index 0000000..90a88d0
--- /dev/null
@@ -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"