You may know that feeling when you purchase a piece of hardware only to find that it has sub-par or even zero support for Linux or open source software in general. Maybe you forgot to do the research ahead of time, or maybe you just couldn’t bear the heat of money burning a hole in your pocket. You think, perhaps if I just wait it out, it will eventually be supported…​ You swiftly come to realize that with the proliferation of different competing hardware, there is a very slim chance of that ever happening.

This is what happened to me when I saw a great deal on a TP-Link EAP660 HD access point. I love tinkering with networking hardware, especially when it comes to Wi-Fi devices, so missing that opportunity was out of the question.

So I arrived home after a long day of work, ready to install OpenWrt on my shiny new access point. I go to the nice and convenient OpenWrt Firmware Selector, start typing in my device model, and…​ nothing. There was no support for my device. I scoured the web for any information I could find on the device; perhaps someöne made a third-party fork…​ nothing. In fact, I could find no documentation on the device other than the official user manual and firmware downloads. I realized there was only one thing I could do if I was going to have it my way. "Why not just make do with the officially supported device firmware?!" I hear you scream. You may be right…​ but that’s not fun.

So I return to the OpenWrt website. After further search, I found there was little documentation for my device’s chipset in general. This left me with one option: the source code.

Getting a Grasp on Things

Let me start by being honest and saying that getting a grasp on other people’s source code is not my strong suit, especially with large code bases. I found this out pretty quickly when trying to make changes to the QEMU codebase to emulate a new peripheral. It just didn’t make sense to me. There were cross- references everywhere, causing me to have to jump through tens of files just to get a foothold on a single function. I just can’t stand this approach to programming, and maybe this is something I will write about in a future article, but who am I to judge? This hurdle notwithstanding, I had a lot less of a problem this time. OpenWrt has a beautiful codebase, and their documentation is excellent.

After perusing the source code, I found that if my device had a standard flash layout among other things, adding support shouldn’t be that hard. Specifically, I would have to create a Device Tree for my device, dump the calibration data, and change a few boot scripts.

Where to Start

Knowing a thing or two about reverse engineering, I tried to see how I could access the serial console and get a boot log dump. This would tell me what software was running on the unit and the devices being initialized. Having experience with electronics, I chose to rip the bandage off and crack open the housing. I immediately identified an unpopulated 4-pin DuPont connection, and subsequently soldered on the pins. I then closed up the device and cut a hole with a "Dremel" a.k.a. side-cutters in the housing to allow access to the pins.

I then attached my serial adapter to the pins, turned the unit on, and…​ nothing. After thinking for a moment, I decided to open up the device again. I found that the board was missing 3 very tiny resistors. There was no way I was going to buy these, as I was pretty sure you would have to buy them in bulk and I would have no use for the extras. Instead, I assumed they were zero ohms and just bridged the pads. I did not bridge the pad for the 3V pin since I didn’t want to chance accidentally killing the UART chip by applying 5 volts.

Examining the Console Dump

After powering on again, it worked. I got a nice dump of the AP booting up. Here is a sample:

U-Boot 2016.01 (Jan 04 2024 - 11:40:20 +0800)

DRAM:  smem ram ptable found: ver: 1 len: 4
1 GiB
NAND:  Could not find nand_gpio in dts, using defaults
ONFI device found
ID = 158061c8
Vendor = c8
Device = 61
SF: Unsupported flash IDs: manuf ff, jedec ffff, ext_jedec ffff
ipq_spi: SPI Flash not found (bus/cs/speed/mode) = (0/0/48000000/0)
128 MiB
MMC:   sdhci: Node Not found, skipping initialization

PCI1 is not defined in the device tree
In:    serial@78B3000
Out:   serial@78B3000
Err:   serial@78B3000
machid: 8010006
MMC Device 0 not found
No ART partition found
Find boot alter flag!
[board_info_detect:1953] get config_name ok:config@EAP660_HD_1_0_0!
[gpio_special_config 198]led gpio:42 yellow:0
reset button is not pressed for 10s.
turn on led
Hit Ctrl+B to stop autoboot:  0

This already tells us a lot of things about the hardware. First of all, it is running the U-Boot bootloader and has 1 GiB of memory. One thing that stood out to me is that there is no ART partition. This is unusual, since most of the time that partition has calibration data and is in general a very important device-specific partition. Looking further, we find the linux version it is running, and, to my suprise, that it runs OpenWrt.

[    0.000000] Linux version 4.4.60 (jenkins@sohoiapbuild) (gcc version 5.2.0
(OpenWrt GCC 5.2.0 ff0b127+r49254) ) #1 SMP PREEMPT Thu Mar 14 10:16:45 CST 2024

Walking Through the Firmware Image

The next obvious step was to run binwalk on the stock firmware image from TP-Link. This revealed several device tree blobs that I could decompile, as well as the file system images. Starting with the file system, I was able to find the calibration data location by looking at some config files, but I was not able to find them in the image directly as the locations only applied to a running system. I opted to extract them through the SSH console instead. From there, I converted the file to the correct format for OpenWrt and made a pull request to the separate "caldata" repository.

The device trees were a different animal. Even though I had the source, I could not just add it to OpenWrt and call it a day. There is a different structure and naming convention used by OpenWrt than by whoëver developed the stock firmware. Many of the devices were inactive or missing from the board as well, meaning this was probably part of some generic SDK rather than specifically tailored to the board.

Constructing the Device Tree

I began writing the device tree by looking at a Netgear device with the same chipset. Essentially, this device tree tells the kernel what devices are on the system, at which ports to find them, and what their parameters are. The existing files were enough for me to know what names alias to which device in the tree, such as mdio for the Qualcomm Ethernet MDIO interface and wifi for the Atheros Wi-Fi interface.

The boot logs and extracted device tree were not enough to tell me which GPIO pins to enumerate, so I did some educated trial and error. In the end, I found that the LED indicator used pin 42 on the TLMM, the reset button used pin 50, and the MDIO used GPIO 68 and 69. After that, I had to list the NAND partitions and tidy it up a bit.

Build and Boot Scripts

Next in the process was to add the target and several references to the build system. Essentially, I registered the new target as a build option and was able to see it when running make menuconfig. This not only created a build target as a whole, but also made a new variant of the ipq-wifi package. After this, I added the vendor, block size, image type, and other parameters to the makefile.

In the boot scripts, I defined the name of the ethernet interface as lan and added the commands to extract the MAC address from the data partition. To make sure the device would only assign its manufacturer-asseigned MACs and no more, I had to tinker around with guest networks on the stock image and see how they were assigned. I also had to add shell functions to mount the partitions and do system upgrades the "TP-Link Way". This is because the AP uses A/B partitioning for updates, which is different from other devices of its chipset variëty. I was able to reuse shell code from another TP-Link device which also uses A/B partitioning.

Finishing Touches

After everything was in order, I had to test out my firmware, so I booted it using the TFTP loader through U-Boot and installed it with a sysupgrade command. I ironed out the last of the bugs and was ready to ship it.

One of the requirements for submitting a new device to OpenWrt is to make a list of hardware specifications, like the amount of memory, Wi-Fi standards, and part numbers of chips if possible. I was able to find this information by looking up its FCC ID and looking at the images (I didn’t want to dismantle the device for a third time). Here is what I gathered:

  • SoC: Qualcomm IPQ8072A (64-bit Quad-core Arm Cortex-A53 @ 2200MHz)

  • Memory: 2x ESMT M15T4G16256A-DEBG2G (1 GiB DDR3-1866 13-13-13)

  • Serial Port: 3v3 TTL 115200n8

  • Wi-Fi: QCN5054 (4x4 5 GHz 802.11ax)

  • Wi-Fi: QCN5024 (4x4 2.4 GHz 802.11b/g/n/ax)

  • Ethernet: QCA8081 (10/100/1000/2.5GBASE-T)

  • Flash: Winbond W29N01HZSINF (128 MiB)

  • LEDs: 1x Blue Status (GPIO 42 Active High)

  • Buttons: 1x Reset (GPIO 50 Active Low)

Another requirement is to document how to install the firmware. Unfortunately, the official image format has not been deciphered, so installing it through the web interface would not be an option. I tried to reverse engineer it myself, but decided to give up after a few hours. It appeared that there was a clever signature scheme within the format that I did not have time to deal with. If support for the format were to be eventually made, one would have to disable signature checks over SSH in order to install it, and besides, I already have access to U-Boot.

Making My PR

I committed my changes and made a pull request to the official GitHub repository. I was informed by the reviewers that there were some newer script functions I could use instead, and that there were some redundant items in the device tree that could be removed. After fulfilling the requested changes, my commit was finally approved and merged. You can find it here.

Conclusion

Overall, I enjoyed working on this project. I was able to put my knowledge to good use, while also getting the result I wanted. While most people would never have a use for this contribution and it is kind of a niche, I definitely learned a lot about U-Boot and how Linux works under the hood. At the time of writing, the device support has not made its way to a release version, but it is set to release as part of v24.10. I’m looking forward to being able to set up my home lab with stable and maintained software.