diff --git a/include/image-commands.mk b/include/image-commands.mk index 2e129e0347d..aa48e19399e 100644 --- a/include/image-commands.mk +++ b/include/image-commands.mk @@ -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) \ diff --git a/package/boot/uboot-envtools/files/qualcommax_ipq60xx b/package/boot/uboot-envtools/files/qualcommax_ipq60xx index eec99ce8f97..77c96da24ac 100644 --- a/package/boot/uboot-envtools/files/qualcommax_ipq60xx +++ b/package/boot/uboot-envtools/files/qualcommax_ipq60xx @@ -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" ] && \ diff --git a/package/firmware/ipq-wifi/Makefile b/package/firmware/ipq-wifi/Makefile index 679a1442115..9821e39a85f 100644 --- a/package/firmware/ipq-wifi/Makefile +++ b/package/firmware/ipq-wifi/Makefile @@ -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)) diff --git a/scripts/tplink-mkimage-2022.py b/scripts/tplink-mkimage-2022.py new file mode 100755 index 00000000000..4db69409c9e --- /dev/null +++ b/scripts/tplink-mkimage-2022.py @@ -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()) diff --git a/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts b/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts new file mode 100644 index 00000000000..165fc3ef1b1 --- /dev/null +++ b/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts @@ -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 +#include +#include + +/ { + 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 = ; + }; + }; + + leds { + compatible = "gpio-leds"; + + led_sys_amber: led-0 { + function = "system"; + color = ; + gpios = <&tlmm 35 GPIO_ACTIVE_HIGH>; + }; + + led_sys_green: led-1 { + function = "system"; + color = ; + 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 = ; + switch_mac_mode1 = ; + 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"; +}; diff --git a/target/linux/qualcommax/image/ipq60xx.mk b/target/linux/qualcommax/image/ipq60xx.mk index 0b7cd413c36..c7e0be1181e 100644 --- a/target/linux/qualcommax/image/ipq60xx.mk +++ b/target/linux/qualcommax/image/ipq60xx.mk @@ -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) diff --git a/target/linux/qualcommax/ipq60xx/base-files/etc/board.d/02_network b/target/linux/qualcommax/ipq60xx/base-files/etc/board.d/02_network index ecc9e57117b..44c0ba7049e 100644 --- a/target/linux/qualcommax/ipq60xx/base-files/etc/board.d/02_network +++ b/target/linux/qualcommax/ipq60xx/base-files/etc/board.d/02_network @@ -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 diff --git a/target/linux/qualcommax/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata b/target/linux/qualcommax/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata index 3380cc8653f..cf3e400586b 100644 --- a/target/linux/qualcommax/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata +++ b/target/linux/qualcommax/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata @@ -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 ;; diff --git a/target/linux/qualcommax/ipq60xx/base-files/lib/preinit/09_mount_factory_data b/target/linux/qualcommax/ipq60xx/base-files/lib/preinit/09_mount_factory_data new file mode 100644 index 00000000000..97608db55dd --- /dev/null +++ b/target/linux/qualcommax/ipq60xx/base-files/lib/preinit/09_mount_factory_data @@ -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 diff --git a/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh b/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh index 411570715c2..f9d446ff1f7 100644 --- a/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh +++ b/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh @@ -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)"