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@cjdns.fr>
Link: https://github.com/openwrt/openwrt/pull/19021
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
This commit is contained in:
Caleb James DeLisle 2025-09-01 10:10:56 +00:00 committed by Hauke Mehrtens
parent 73d0f92460
commit ef2785a2d0
4 changed files with 336 additions and 1 deletions

View file

@ -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 "$@"

View file

@ -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>;
};
};
};

View 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))

View file

@ -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"