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>
--- /dev/null
+#!/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 "$@"
--- /dev/null
+// 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>;
+ };
+ };
+};
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))
--- /dev/null
+#!/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"