forked from immortalwrt/immortalwrt
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]> Link: openwrt/openwrt#14922 Signed-off-by: Robert Marko <[email protected]>
- Loading branch information
Showing
10 changed files
with
490 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) |
151 changes: 151 additions & 0 deletions
151
target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"; | ||
}; |
Oops, something went wrong.