qualcommax: ipq60xx: add TP-Link EAP610-Outdoor support

TP-Link EAP610-Outdoor is a 802.11ax AP claiming AX1800 support. It is
wall or pole mountable, and rated for outdoor use. It can only be
powered via PoE.

Specifications:
---------------
* CPU: Qualcomm IPQ6018 Quad core Cortex-A53
* RAM: 512 MB
* Storage: ESMT PSR1GA30DT 128MB NAND
* Ethernet:
  * Gigabit RJ45 port with PoE input
* WLAN:
  * 2.4GHz/5GHz
* LEDs:
  * Multi-color System LED (Green/Amber)
* Buttons:
  * 1x Reset
* UART: 4-pin unpopulated header
  * 1.8 V level, Pinout 1 - TX, 2 - RX, 3 - GND, 4 - 1.8V

Installation:
=============

Web UI method
-------------

Set up the device using the vendor's web UI. After that go to
Management->SSH and enable the "SSH Login" checkbox. Select "Save".
The connect to the machine via SSH:

    ssh -o hostkeyalgorithms=ssh-rsa <ip_of_device>

Disable signature verification:

    cliclientd stopcs

Rename the "-web-ui-factory" image to something less than 63
characters, maintaining the ".bin" suffix.
 * Go to System -> Firmware Update.
 * Under "New Firmware File", click "Browse" and select the image
 * Select "Update" and confirm by clicking "OK".

If the update fails, the web UI should show an error message.
Otherwise, the device should reboot into OpenWRT.

TFTP method
-----------

To flash via tftp, first place the initramfs image on the TFTP server.

    setenv serverip <ip of tftp server>
    setenv ipaddr <ip in same subnet as tftp server>
    tftpboot tplink_eap610-outdoor-initramfs-uImage.itb
    bootm

This should boot OpenWRT. Once booted, flash the sysupgrade.bin image
using either luci or the commandline.

The tplink2022 image format
============================

The vendor images of this device are packaged in a format that does
not match any previous tplink formats. In order for flashing to work
from the vendor's web UI, firmware updates need to be packaged in
this format. The `tplink-mkimage-2022.py` is provided for this
purpose.

This script can also analyze vendor images, and extract the required
"support" string. This string is checked by the vendor firmware, and
images with a missing or incorrect string are rejected.

Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/14922
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Alexandru Gagniuc 2022-08-07 10:15:35 -05:00 committed by Robert Marko
parent 3f87c5ac42
commit a00ff9f6d1
10 changed files with 490 additions and 0 deletions

View File

@ -626,6 +626,14 @@ define Build/sysupgrade-tar
$@
endef
define Build/tplink-image-2022
$(TOPDIR)/scripts/tplink-mkimage-2022.py \
--create $@.new \
--rootfs $@ \
--support "$(TPLINK_SUPPORT_STRING)"
@mv $@.new $@
endef
define Build/tplink-safeloader
-$(STAGING_DIR_HOST)/bin/tplink-safeloader \
-B $(TPLINK_BOARD_ID) \

View File

@ -25,6 +25,10 @@ netgear,wax214)
[ -n "$idx" ] && \
ubootenv_add_uci_config "/dev/mtd$idx" "0x0" "0x40000" "0x20000"
;;
tplink,eap610-outdoor)
idx="$(find_mtd_index 0:appsblenv)"
[ -n "$idx" ] && \
ubootenv_add_uci_config "/dev/mtd$idx" "0x0" "0x40000" "0x20000"
yuncore,fap650)
idx="$(find_mtd_index 0:appsblenv)"
[ -n "$idx" ] && \

View File

@ -55,6 +55,7 @@ ALLWIFIBOARDS:= \
redmi_ax6 \
skspruce_wia3300-20 \
spectrum_sax1v1k \
tplink_eap610-outdoor \
tplink_eap620hd-v1 \
tplink_eap660hd-v1 \
wallys_dr40x9 \
@ -185,6 +186,7 @@ $(eval $(call generate-ipq-wifi-package,prpl_haze,prpl Haze))
$(eval $(call generate-ipq-wifi-package,redmi_ax6,Redmi AX6))
$(eval $(call generate-ipq-wifi-package,skspruce_wia3300-20,SKSpruce WIA3300-20))
$(eval $(call generate-ipq-wifi-package,spectrum_sax1v1k,Spectrum SAX1V1K))
$(eval $(call generate-ipq-wifi-package,tplink_eap610-outdoor,TPLink EAP610-Outdoor))
$(eval $(call generate-ipq-wifi-package,tplink_eap620hd-v1,TP-Link EAP620 HD v1))
$(eval $(call generate-ipq-wifi-package,tplink_eap660hd-v1,TP-Link EAP660 HD v1))
$(eval $(call generate-ipq-wifi-package,wallys_dr40x9,Wallys DR40X9))

198
scripts/tplink-mkimage-2022.py Executable file
View File

@ -0,0 +1,198 @@
#!/usr/bin/env python3
'''A program for manipulating tplink2022 images.
A tplink2022 is an image format encountered on TP-Link devices around the year
2022. This was seen at least on the EAP610-Outdoor. The format is a container
for a rootfs, and has optional fields for the "software" version. It also
requires a "support" string that describes the list of compatible devices.
This module is intended for creating such images with an OpenWRT UBI image, but
also supports analysis and extraction of vendor images. Altough tplink2022
images can be signed, this program does not support signing image.
To get an explanation of the commandline arguments, run this program with the
"--help" argument.
'''
import argparse
import hashlib
import os
import pprint
import struct
def decode_header(datafile):
'''Read the tplink2022 image header anbd decode it into a dictionary'''
header = {}
fmt = '>2I'
datafile.seek(0x1014)
raw_header = datafile.read(8)
fields = struct.unpack(fmt, raw_header)
header['rootfs_size'] = fields[0]
header['num_items'] = fields[1]
header['items'] = []
rootfs = {}
rootfs['name'] = 'rootfs.ubi'
rootfs['offset'] = 0
rootfs['size'] = header['rootfs_size']
header['items'].append(rootfs)
for _ in range(header['num_items']):
entry = datafile.read(0x2c)
fmt = '>I32s2I'
fields = struct.unpack(fmt, entry)
section = {}
section['name'] = fields[1].decode("utf-8").rstrip('\0')
section['type'] = fields[0]
section['offset'] = fields[2]
section['size'] = fields[3]
header['items'].append(section)
return header
def extract(datafile):
'''Extract the sections of the tplink2022 image to separate files'''
header = decode_header(datafile)
pretty = pprint.PrettyPrinter(indent=4, sort_dicts=False)
pretty.pprint(header)
for section in header['items']:
datafile.seek(0x1814 + section['offset'])
section_contents = datafile.read(section['size'])
with open(f"{section['name']}.bin", 'wb') as section_file:
section_file.write(section_contents)
with open('leftover.bin', 'wb') as extras_file:
extras_file.write(datafile.read())
def get_section_contents(section):
'''I don't remember what this does. It's been a year since I wrote this'''
if section.get('data'):
data = section['data']
elif section.get('file'):
with open(section['file'], 'rb') as section_file:
data = section_file.read()
else:
data = bytes()
if section['size'] != len(data):
raise ValueError("Wrong section size", len(data))
return data
def write_image(output_image, header):
'''Write a tplink2022 image with the contents in the "header" dictionary'''
with open(output_image, 'w+b') as out_file:
# header MD5
salt = [ 0x7a, 0x2b, 0x15, 0xed,
0x9b, 0x98, 0x59, 0x6d,
0xe5, 0x04, 0xab, 0x44,
0xac, 0x2a, 0x9f, 0x4e
]
out_file.seek(4)
out_file.write(bytes(salt))
# unknown section
out_file.write(bytes([0xff] * 0x1000))
# Table of contents
raw_header = struct.pack('>2I', header['rootfs_size'],
header['num_items'])
out_file.write(raw_header)
for section in header['items']:
if section['name'] == 'rootfs.ubi':
continue
hdr = struct.pack('>I32s2I',
section.get('type', 0),
section['name'].encode('utf-8'),
section['offset'],
section['size']
)
out_file.write(hdr)
for section in header['items']:
out_file.seek(0x1814 + section['offset'])
out_file.write(get_section_contents(section))
size = out_file.tell()
out_file.seek(4)
md5_sum = hashlib.md5(out_file.read())
out_file.seek(0)
out_file.write(struct.pack('>I16s', size, md5_sum.digest()))
def encode_soft_verson():
'''Not sure of the meaning of version. Also doesn't appear to be needed.'''
return struct.pack('>4B1I2I', 0xff, 1, 0 ,0, 0x2020202, 30000, 1)
def create_image(output_image, root, support):
'''Create an image with a ubi "root" and a "support" string.'''
header = {}
header['rootfs_size'] = os.path.getsize(root)
header['items'] = []
rootfs = {}
rootfs['name'] = 'rootfs.ubi'
rootfs['file'] = root
rootfs['offset'] = 0
rootfs['size'] = header['rootfs_size']
header['items'].append(rootfs)
support_list = {}
support_list['name'] = 'support-list'
support_list['data'] = support.replace(" ", "\r\n").encode('utf-8')
support_list['offset'] = header['rootfs_size']
support_list['size'] = len(support_list['data'])
header['items'].append(support_list)
sw_version = {}
sw_version['name'] = 'soft-version'
sw_version['type'] = 1
sw_version['data'] = encode_soft_verson()
sw_version['offset'] = support_list['offset'] + support_list['size']
sw_version['size'] = len(sw_version['data'])
header['items'].append(sw_version)
header['num_items'] = len(header['items']) - 1
write_image(output_image, header)
def main(args):
'''We support image analysis,extraction, and creation'''
if args.extract:
with open(args.image, 'rb') as image:
extract(image)
elif args.create:
if not args.rootfs or not args.support:
raise ValueError('To create an image, specify rootfs and support list')
create_image(args.image, args.rootfs, args.support)
else:
with open(args.image, 'rb') as image:
header = decode_header(image)
pretty = pprint.PrettyPrinter(indent=4, sort_dicts=False)
pretty.pprint(header)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='EAP extractor')
parser.add_argument('--info', action='store_true')
parser.add_argument('--extract', action='store_true')
parser.add_argument('--create', action='store_true')
parser.add_argument('image', type=str,
help='Name of image to create or decode')
parser.add_argument('--rootfs', type=str,
help='When creating an EAP image, UBI image with rootfs and kernel')
parser.add_argument('--support', type=str,
help='String for the "support-list" section')
main(parser.parse_args())

View File

@ -0,0 +1,151 @@
// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/dts-v1/;
#include "ipq6018.dtsi"
#include "ipq6018-cp-cpu.dtsi"
#include "ipq6018-ess.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/leds/common.h>
/ {
model = "TP-Link EAP610-Outdoor";
compatible = "tplink,eap610-outdoor", "qcom,ipq6018";
aliases {
serial0 = &blsp1_uart3;
led-boot = &led_sys_green;
led-failsafe = &led_sys_amber;
led-running = &led_sys_green;
led-upgrade = &led_sys_amber;
};
chosen {
stdout-path = "serial0:115200n8";
bootargs-append = " ubi.block=0,rootfs root=/dev/ubiblock0_1";
};
keys {
compatible = "gpio-keys";
reset {
label = "reset";
gpios = <&tlmm 9 GPIO_ACTIVE_LOW>;
linux,code = <KEY_RESTART>;
};
};
leds {
compatible = "gpio-leds";
led_sys_amber: led-0 {
function = "system";
color = <LED_COLOR_ID_AMBER>;
gpios = <&tlmm 35 GPIO_ACTIVE_HIGH>;
};
led_sys_green: led-1 {
function = "system";
color = <LED_COLOR_ID_GREEN>;
gpios = <&tlmm 37 GPIO_ACTIVE_HIGH>;
};
};
gpio-restart {
compatible = "gpio-restart";
gpios = <&tlmm 61 GPIO_ACTIVE_LOW>;
open-source;
};
};
&blsp1_uart3 {
pinctrl-0 = <&serial_3_pins>;
pinctrl-names = "default";
status = "okay";
};
&tlmm {
mdio_pins: mdio-pins {
mdc {
pins = "gpio64";
function = "mdc";
drive-strength = <8>;
bias-pull-up;
};
mdio {
pins = "gpio65";
function = "mdio";
drive-strength = <8>;
bias-pull-up;
};
};
led_enable {
gpio-hog;
output-high;
gpios = <36 GPIO_ACTIVE_HIGH>;
line-name = "enable-leds";
};
};
&dp5 {
phy-handle = <&rtl8211f_4>;
phy-mode = "sgmii";
label = "lan";
status = "okay";
};
&edma {
status = "okay";
};
&mdio {
pinctrl-0 = <&mdio_pins>;
pinctrl-names = "default";
reset-gpios = <&tlmm 77 GPIO_ACTIVE_LOW>;
reset-delay-us = <10000>;
reset-post-delay-us = <50000>;
status = "okay";
rtl8211f_4: ethernet-phy@4 {
reg = <4>;
};
};
&switch {
switch_lan_bmp = <ESS_PORT5>;
switch_mac_mode1 = <MAC_MODE_SGMII_CHANNEL0>;
status = "okay";
qcom,port_phyinfo {
port@4 {
port_id = <5>;
phy_address = <4>;
};
};
};
&qpic_bam {
status = "okay";
};
&qpic_nand {
status = "okay";
nand@0 {
reg = <0>;
nand-ecc-strength = <4>;
nand-ecc-step-size = <512>;
nand-bus-width = <8>;
};
};
&wifi {
ieee80211-freq-limit = <2402000 5835000>;
qcom,ath11k-calibration-variant = "TP-Link-EAP610-Outdoor";
status = "okay";
};

View File

@ -68,6 +68,24 @@ define Device/qihoo_360v6
endef
TARGET_DEVICES += qihoo_360v6
define Device/tplink_eap610-outdoor
$(call Device/FitImage)
$(call Device/UbiFit)
DEVICE_VENDOR := TP-Link
DEVICE_MODEL := EAP610-Outdoor
BLOCKSIZE := 128k
PAGESIZE := 2048
SOC := ipq6018
DEVICE_PACKAGES := ipq-wifi-tplink_eap610-outdoor
IMAGES += web-ui-factory.bin
IMAGE/web-ui-factory.bin := append-ubi | tplink-image-2022
TPLINK_SUPPORT_STRING := SupportList: \
EAP610-Outdoor(TP-Link|UN|AX1800-D):1.0 \
EAP610-Outdoor(TP-Link|JP|AX1800-D):1.0 \
EAP610-Outdoor(TP-Link|CA|AX1800-D):1.0
endef
TARGET_DEVICES += tplink_eap610-outdoor
define Device/yuncore_fap650
$(call Device/FitImage)
$(call Device/UbiFit)

View File

@ -23,6 +23,9 @@ ipq60xx_setup_interfaces()
qihoo,360v6)
ucidef_set_interfaces_lan_wan "lan1 lan2 lan3" "wan"
;;
tplink,eap610-outdoor)
ucidef_set_interface_lan "lan" "dhcp"
;;
linksys,mr7350|\
yuncore,fap650)
ucidef_set_interfaces_lan_wan "lan1 lan2 lan3 lan4" "wan"
@ -51,6 +54,10 @@ ipq60xx_setup_macs()
wan_mac=$(macaddr_add "$lan_mac" 1)
label_mac=$lan_mac
;;
tplink,eap610-outdoor)
label_mac=$(get_mac_binary /tmp/factory_data/default-mac 0)
lan_mac=$label_mac
;;
esac
[ -n "$lan_mac" ] && ucidef_set_interface_macaddr "lan" $lan_mac

View File

@ -32,6 +32,13 @@ case "$FIRMWARE" in
ath11k_patch_mac $(macaddr_add $label_mac 2) 1
ath11k_set_macflag
;;
tplink,eap610-outdoor)
caldata_from_file "/tmp/factory_data/radio" 0 0x10000
label_mac=$(get_mac_binary /tmp/factory_data/default-mac 0)
ath11k_patch_mac $label_mac 1
ath11k_patch_mac $(macaddr_add $label_mac 1) 0
ath11k_set_macflag
;;
yuncore,fap650)
caldata_extract "0:art" 0x1000 0x20000
;;

View File

@ -0,0 +1,19 @@
#!/bin/sh
preinit_mount_factory_data() {
local mtd_path
. /lib/functions.sh
. /lib/functions/system.sh
case $(board_name) in
tplink,eap610-outdoor)
mtd_path=$(find_mtd_chardev "factory_data")
ubiattach --dev-path="$mtd_path" --devn=1
mkdir /tmp/factory_data
mount -o ro,noatime -t ubifs ubi1:ubi_factory_data /tmp/factory_data
;;
esac
}
boot_hook_add preinit_main preinit_mount_factory_data

View File

@ -4,6 +4,79 @@ REQUIRE_IMAGE_METADATA=1
RAMFS_COPY_BIN='fw_printenv fw_setenv head'
RAMFS_COPY_DATA='/etc/fw_env.config /var/lock/fw_printenv.lock'
remove_oem_ubi_volume() {
local oem_volume_name="$1"
local oem_ubivol
local mtdnum
local ubidev
mtdnum=$(find_mtd_index "$CI_UBIPART")
if [ ! "$mtdnum" ]; then
return
fi
ubidev=$(nand_find_ubi "$CI_UBIPART")
if [ ! "$ubidev" ]; then
ubiattach --mtdn="$mtdnum"
ubidev=$(nand_find_ubi "$CI_UBIPART")
fi
if [ "$ubidev" ]; then
oem_ubivol=$(nand_find_volume "$ubidev" "$oem_volume_name")
[ "$oem_ubivol" ] && ubirmvol "/dev/$ubidev" --name="$oem_volume_name"
fi
}
tplink_get_boot_part() {
local cur_boot_part
local args
# Try to find rootfs from kernel arguments
read -r args < /proc/cmdline
for arg in $args; do
local ubi_mtd_arg=${arg#ubi.mtd=}
case "$ubi_mtd_arg" in
rootfs|rootfs_1)
echo "$ubi_mtd_arg"
return
;;
esac
done
# Fallback to u-boot env (e.g. when running initramfs)
cur_boot_part="$(/usr/sbin/fw_printenv -n tp_boot_idx)"
case $cur_boot_part in
1)
echo rootfs_1
;;
0|*)
echo rootfs
;;
esac
}
tplink_do_upgrade() {
local new_boot_part
case $(tplink_get_boot_part) in
rootfs)
CI_UBIPART="rootfs_1"
new_boot_part=1
;;
rootfs_1)
CI_UBIPART="rootfs"
new_boot_part=0
;;
esac
fw_setenv -s - <<-EOF
tp_boot_idx $new_boot_part
EOF
remove_oem_ubi_volume ubi_rootfs
nand_do_upgrade "$1"
}
platform_check_image() {
return 0;
}
@ -55,6 +128,9 @@ platform_do_upgrade() {
qihoo,360v6)
nand_do_upgrade "$1"
;;
tplink,eap610-outdoor)
tplink_do_upgrade "$1"
;;
yuncore,fap650)
[ "$(fw_printenv -n owrt_env_ver 2>/dev/null)" != "7" ] && yuncore_fap650_env_setup
local active="$(fw_printenv -n owrt_slotactive 2>/dev/null)"