Mercusys MR80X v2: Working OpenWrt with patched ath11k!

​Hi everyone,

​Since the old discussion thread for this device got closed (old thread here), I decided to open a new one to share a breakthrough and push the development forward for everyone who wants OpenWrt on this router.

​I actually managed to get OpenWrt running and successfully flashed on the Mercusys MR80X v2!

​As many of you know, the 256MB RAM limitation was a massive bottleneck for the ath11k driver on the IPQ5018, constantly leading to OOM (Out of Memory) crashes. To bypass this, I used a patched ath11k driver compiled with the latest OpenWrt version.

​Current Status:

  • ​Base: Latest OpenWrt version
  • ​Wi-Fi: Fully working (QCN6102) via the patched ath11k driver
  • ​Stability: The router has been running completely stable for the last 24 hours without any OOM issues or reboots.

​My goal with this thread is to collaborate, share findings, and eventually help bring a stable community build (or even official support) to this device.

​I will clean up my work and share the patches, build details, and flash instructions shortly. In the meantime: Are there any devs or users here who still have their MR80X v2 and are willing to test the build/patches once I upload them?

Cleanup is done — everything is now in a repo:

https://github.com/Gameplayer-1-8/openwrt_mrx80_v2

It contains a sysupgrade and an initramfs image, the full source changes against OpenWrt main (ce16dd8, kernel 6.12.100), hardware documentation, and the tools I used to pull the device data apart.

What works

Both radios, DSA switch with real wan/lan1/lan2/lan3 ports, LuCI, sysupgrade, boots from NAND on its own, serial console in and out. Per-unit WiFi calibration is read from 0:art at runtime, so no hand-injected caldata.

Roughly 50 MB RAM free with both radios up. 5 GHz tops out around 350 Mbit with both cores saturated — mainline ath11k has no NSS offload for IPQ5018.

Correction to my first post

I have to walk back what I wrote at the top of this thread. Memory was never the problem on this device. There were no OOM kills, and the box idles at ~50 MB available with both radios loaded. The 911-ath11k-reduce-dma-buffer patch is in the repo, but it is inherited, not the fix — I ended up raising its ring sizes again, because DP_TX_COMP_RING_SIZE also caps in-flight TX descriptors and cutting it hurts throughput for no benefit here.

What actually looked like a memory problem was a boot that silently stopped right after kmodloader: done. That turned out to be nothing at all: with no network configured that is simply the last line a successful boot prints, and console input was dead so the board looked hung. I chased that for a long time. If you are debugging this SoC, verify the box is actually stuck before assuming it is — a heartbeat script writing to /dev/console settles it in one boot.

The five things that actually cost time

Each verified on hardware, all documented in the repo README.

1. NAND ECC strength is 4, not 8. U-Boot prints it: ECC:4-bit. Decoding at <8> returns data that looks perfectly clean — stable across reads, no ECC errors logged — but with silently flipped bits. It corrupted caldata read from 0:art so the WiFi firmware asserted in phyrf_bdf.c:605, produced mtdsplit: error occured while reading from "rootfs", and made tp_data report "both volume tables are corrupted" when the volume was in fact intact. Get this right before writing anything to NAND.

2. The RTL8367S reset is GPIO 26, and U-Boot hides the problem. U-Boot only runs rtk_switch_init when it falls through to its prompt. On the plain autoboot path into NAND it never touches the switch, so the chip stays in reset and MDIO reads return 0xffff forever. Every TFTP test goes through the prompt, so the switch works there and fails only from flash. Do not be misled by the OEM DTB's //soc/mdio@90000 phy-reset-gpio = <0x06 0x27 0x00> (GPIO 39) — that is the MDIO/PHY reset, a different pin for a different job.

3. The CPU link must be 1G SGMII, not 2.5G HSGMII. U-Boot sets the switch to 2.5G and the OEM runs it that way, but 3.125 GBaud is not reliable on this board:

2.5G HSGMII 1G SGMII
ping 32 B ×50 6 % loss 0 %
ping 1400 B DF ×50 4–13 % loss 0 %
20 × HTTP GET 8–11 / 20 20 / 20

The loss is invisible in every counter (errs 0 drop 0 fifo 0 on the conduit) because frames mangled on the SerDes are dropped by the PCS layer before the MAC sees a frame. 1 Gbit is ample here.

4. DEVICE_DTS_CONFIG must be config@mp02.1 to match this board's machid 0x8040000. A manual bootm works with any name because U-Boot falls back to the FIT default, so it looks cosmetic — but bootipq selects by machid, prints Config not available and drops into the OEM HTTP recovery mode.

5. No pinctrl on &blsp1_uart1, or console RX dies while TX keeps working (printk busy-polls, so the port looks healthy while not one RX interrupt arrives). The mux value is not wrong; inheriting U-Boot's pin setup is what works.

Caveats before anyone flashes this

  • The MAC address is hard-coded in the DTS for my unit. Yours is in the tp_data UBI volume, file default-mac, six raw bytes. The README explains how to read it. Reading it at runtime is the main thing still missing for this to be upstream-ready.
  • LED-to-port mapping and which socket is WAN come from the reference fork and the GPL sources and are not physically confirmed.
  • The ath11k ring sizes shipped here are not throughput-tested.

Recovery is always available: sysupgrade never touches 0:SBL1 or 0:APPSBL, so U-Boot survives and you can always TFTP an initramfs back.

Is this patch merged or upstreamed ?

No, neither is upstream.

The one my first post referred to is hzyitc's 911-ath11k-reduce-dma-buffer-to-save-memeory, which circulates in various IPQ5018 forks but is not in OpenWrt main. The tracked ath11k patches there go straight from 910 to 920.

More to the point, and I corrected this in the post above: it is not what made this device work. Memory was never the constraint here. The box idles at ~50 MB available with both radios up and there were never any OOM kills. My first post was simply wrong about the cause. I do ship that patch in the repo, but with its ring sizes raised again, because DP_TX_COMP_RING_SIZE also caps in-flight TX descriptors and shrinking it costs throughput for no benefit on this board. I would rather nobody carried it over expecting it to fix an OOM they do not actually have.

What did fix things was mundane and device-specific: wrong NAND ECC strength in the DTS (4-bit, not 8), a switch reset GPIO that nothing was releasing on the autoboot path, and a CPU link running at 2.5G that this board cannot do cleanly. Details in the post above.

The only patch I wrote myself is 0918-net-dsa-realtek-poll-for-switch, which retries the RTL8367S chip-ID read instead of giving up on the first MDIO access. Not submitted anywhere, and honestly it is belt-and-braces: the actual fix was reset-gpios on the switch node.

Nothing in the repo is upstream-ready as it stands. The blocker is the MAC address, currently hard-coded in the DTS for my unit instead of being read from the tp_data volume at runtime. That is the next thing I want to sort out.

You should create a port of MR3000X based on the current OpenWrt tree (https://github.com/openwrt/openwrt/), preferably testing with kernel 6.18.

No problem at all; I’d be happy to provide that. That would have been my next step, too. Apologies for my poor English, it’s not my native language

The patches at https://github.com/openwrt/openwrt/pull/24197 may be helpful. Maybe they'll solve some of your problems that you solved with your own fixes?

I’ll take a look at it eventually. But in the meantime, without even looking at it, I’ve now got it running on kernel 6.18. Everything works without issues: Wi-Fi, all LEDs, flashing, and sysupgrade. The only thing that isn't working yet is extracting the MAC addresses directly from the device. https://github.com/Gameplayer-1-8/openwrt/tree/mercusys-mr80x-v2-6.18

You've made a few tweaks of your own, but they should be kept to a bare minimum. They may no longer be necessary in 6.18, or they may have been included in the PR I brought to your attention.

Thanks a lot, that PR was exactly the right hint. Three of its patches replaced fixes I had done my own way, so two of my own patches are gone now.

What it changed for me:

  • The SET_FEATURE patch fixes the real cause of the SPI NAND getting stuck in OTP mode on 6.18. The whole flash reads as 0xff and every write fails, it looks like the NAND is dead when in fact nothing is damaged. Good one to know for anyone else moving this SoC to 6.18.
  • 714-01 and 714-02 made my switch polling patch unnecessary and, more importantly, I have to correct my post 3: the board does run 2.5 Gbit fine. The packet loss I measured back then was the SerDes PLL not getting the ~98 ms it needs after the reset, not the hardware.
  • 0191 I had not run into yet, but a bus hang depending on binary layout would have cost me a lot of time later.

Current state, all tested on the device with 6.18.39:

Both radios, all four ports, 2.5 Gbit CPU port, LuCI, sysupgrade, boots from NAND and survives a cold start. The MAC is read from the tp_data volume at runtime now, so the images are no longer bound to my router. That was the last blocker I named in post 5.

One of my patches is left: I have to disable the ESMT OTP registration, otherwise the probe fails with -512 and no MTD device appears at all. The AX55 v1 does not show this with the same chip, so it is something on my board. Details are in the repo.

Source, seven commits on ce16dd8:

Images and documentation:

Thanks again, your hint took out more of my own code than it added.

Hello! I hope you're doing well.

Could you provide the detailed technical specifications for the MR80X V2? If possible, could you also provide some internal photos? I’d like to compare it with the MR80X V5 and see how similar they are.

Click the link in the 1st post ?

Would it be possible to get a complete dump of the V2? I’d like to compare the partition layout with the V5. It appears that the partitions are the same, but I’d like to confirm whether the MAC address locations and other parameters are also stored in the same locations.

Boot log with partition layout is in the old thread too.

Won't obviously answer your question about MAC address location, etc.

Same on v2, my unit is labelled MR80X v2.20, U-Boot reports it as MR80X v2. Storage location is as Gameplayer-1-8 documented in Where the MAC address comes from (https://github.com/Gameplayer-1-8/openwrt_mrx80_v2#where-the-mac-address-comes-from): the tp_data volume, file default-mac, six raw bytes.

# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00080000 00020000 "0:sbl1"
mtd1: 00080000 00020000 "0:mibib"
mtd2: 00040000 00020000 "0:bootconfig"
mtd3: 00040000 00020000 "0:bootconfig1"
mtd4: 00100000 00020000 "0:qsee"
mtd5: 00040000 00020000 "0:devcfg"
mtd6: 00040000 00020000 "0:cdt"
mtd7: 00080000 00020000 "0:appsblenv"
mtd8: 00140000 00020000 "0:appsbl"
mtd9: 00100000 00020000 "0:art"
mtd10: 00080000 00020000 "0:training"
mtd11: 02a00000 00020000 "rootfs"
mtd12: 02a00000 00020000 "rootfs_1"
mtd13: 00840000 00020000 "tp_data"
mtd14: 00440000 00020000 "radio"
mtd15: 00080000 00020000 "data"

# ls -la /tmp/tp_data
-rw-r--r--    1 root     root             6 Sep 22  2024 default-mac
-rw-r--r--    1 root     root            47 Sep 22  2024 device-id
-rw-r--r--    1 root     root          3353 Sep  3  2025 https_cert
-rw-r--r--    1 root     root             9 Sep 22  2024 pin
-rw-r--r--    1 root     root           288 Sep 22  2024 product-info
-rw-r--r--    1 root     root         15168 Sep  3  2025 user-config

# hexdump -C default-mac
00000000  30 16 9d XX XX XX                                 |0.....|
00000006

@Gameplayer-1-8, the wifi offsets in that table do not match the stock firmware. The vendor computes the addresses in /sbin/network_get_firm, shipped in the rootfs and also in the GPL tarball under Iplatform/packages/opensource/base-filesystem/filesystem/sbin/:

local mac=$(getfirm MAC)        # nvrammanager -r <file> -p default-mac

lan             $mac            # base
wan             inc_mac $mac    # base + 1
wireless_2.4    dec_mac $mac    # base - 1
wireless_5      dec_mac x2      # base - 2

inc_mac and dec_mac carry only across the last three octets (if(i>3) in the awk), the OUI half is never touched. The README table has 2.4 GHz at base + 2 and 5 GHz at base + 3.

I've some photos of the unit here: https://www.c127.dev/posts/rtl8367s-sgmii-mr80x-v2-20/

The board is aesthetically identical — very little visual difference. I compiled an initramfs from your branch and it worked on the v5. I have another code version where I copied some of Gameplayer-1-8 stuff and tried to merge the two MR80X boards, and I have some differences in the DTS layouts I made. I have a mechanism to generate a signed Cloud image, but we don't have TP-Link's key, so it won't work unless you patch the APPSBL. I left a pre-built package in this repository, appsbl-toolkit — I'll leave it here for anyone who wants to take a look or experiment with it.

You don't need the key!

In my case, the stock web UI upgrade has 2 verification paths, and the image picks which one runs: nvrammanager reads the fw-type:<name> string at offset 0x14 of the header. Cloud selects RSA signed. Any other value falls back to a keyless MD5 check. So a plain fw-type:MR80X unsigned image is accepted by the stock firmware.

That is how my factory.bin is built, flashed on the v2.20 from the stock web UI:

The generic tplink-image-2022 container is rejected in my case, because it never writes fw-type.

And discovered: The U-Boot recovery web page checks signed images and rejects unsigned images, it can be usefull only for downgrades if the "Unsigned Update" gets removed for some reason.

Submitted the port upstream: https://github.com/openwrt/openwrt/pull/24919

Wired only, against current main. Wi-Fi stays disabled, 256 MB is too little for ath11k, same as the ELECOM WRC-X3000GS2.

Three commits: the spi-qpic-snand ECC fix (half of my boots died in Waiting for root device /dev/ubiblock0_1, also sent to linux-mtd: https://lore.kernel.org/linux-mtd/20260825013848.1056946-1-contact@c127.dev/), the fwup image script, and the device support itself.

CPU port ships at sgmii. At 2500base-x it failed to transmit on 7 of 8 reboots, at sgmii 8 of 8 came up clean.

@Gameplayer-1-8 your thread is linked in the PR.

Thanks for upstreaming this, and the link is fine by me.

A few things from my tree that may be directly useful:

  1. 2500base-x on the CPU port

Same failure mode here (link up, TX dead; sometimes the chip-ID read
failing on first MDIO access). It wasn't the PHY mode, it was timing
around the RTL8367S reset. Two patches fixed it:

target/linux/generic/pending-6.18/
714-01-net-dsa-rtl8365mb-wait-out-full-chip-reset.patch
~900 ms after the reset bit clears. Without it the chip is only
half brought up: CPU transmits, receives nothing.
714-02-net-dsa-rtl8365mb-let-the-serdes-pll-settle-after-dp-reset.patch
~98 ms after the data-path reset for the SerDes PLL. Without it
the link is half-alive: RX fine, TX dead.

With both applied 2500base-x is reliable across reboots here; before
that I was losing 4-13% of frames at 2.5 Gbit.
Commit e839879 in

Worth retrying before settling on sgmii. Note the merged Archer AX55 v1
PR (#24197) lists the same cold-start TX symptom as an open issue and
does not carry these patches, so this may fix both boards.

  1. Non-deterministic boot failures

Two more things beyond the ECC fix, which may overlap with your
"half of my boots died in Waiting for root device":

  • spi-qpic-snand writes the feature value one operation too late,
    i.e. after SET_FEATURE instead of before. Affects ECC enable,
    quad enable, block unlock and OTP. (commit 5e0dda6)
  • ipq5018 CMN PLL: the bus clocks get gated after probe and the next
    bus access hangs the SoC. Depends on binary layout, so it comes and
    goes with unrelated changes. (commit e6569e2)

On 6.18 the ESMT F50D1G41LB also fails to probe because of the new OTP
area registration; I disable it (commit 4394b39). 6.12 is unaffected.

  1. Wi-Fi on 256 MB

You're right that it's tight, but it does run. The one thing needed is
shrinking the ath11k DMA rings
(package/kernel/mac80211/patches/ath11k/
911-ath11k-reduce-dma-buffer-to-save-memeory.patch, commit aa1aa27):

DP_TX_COMP_RING_SIZE 32768 -> 8192
DP_RXDMA_BUF_RING_SIZE 4096 -> 2048
DP_RXDMA_MONITOR_BUF_RING_SIZE 4096 -> 128
DP_RXDMA_MONITOR_DST_RING_SIZE 2048 -> 128

Most of it is the monitor rings across two radios: ~35 MB back.
MemAvailable goes from ~0 kB to ~32 MB with both radios up, stable
here for 24h+ without OOM. Still thin, so I'm not arguing it belongs
in your initial commit - but as a follow-up it beats leaving the
device wired-only for good. Happy to send it on top once you're merged.

  1. MAC addresses, re: the review comment about per-interface MACs

From tp_data/default-mac (6 raw bytes), on my units:

lan / label :xx:xx:xx default-mac
wan default-mac + 1
2.4 GHz default-mac + 2
5 GHz default-mac + 3
(unused gmac default-mac + 4)

Matches what you found in #16. One detail: implement the offset as a
48-bit add rather than a last-octet add - vendor inc_mac/dec_mac carries
into the preceding octets, which matters on units where the last octet
is near 0xff.

I'll flash your factory image on my unit and report back with a
Tested-by on the PR.

It was the PHY mode here, just not the one we were looking at. The UNIPHY PCS hangs off the conduit: ipq5018-ess.dtsi gives gmac1 pcs-handle = <&uniphy0>.

So gmac1's phy-mode sets the SerDes rate, and the CPU port's only reaches the RTL8367S. Leave them disagreeing and the switch runs HSGMII at 312.5 MHz into a SerDes still at 125 MHz.

I have to correct myself, from my post:

That test only moved the CPU port to 2500base-x and left gmac1 at sgmii, so what I measured was the mismatch, not 2500base-x. With both ends at 2.5G it works.

So I tested the image using 6.18.44 without the cited patches, and DTS config:

  • gmac1: using 2500base-x
  • CPU port: using 2500base-x
  • MISC2 @ 0x98218: 0x50 SGMII+
  • eth0: fixed/2500base-x 2.5Gbps

devmem 0x98218 32 reads it, and the results are clear: 8 boots, 8 links at 2.5Gbps HSGMII, 0% loss on every ping recorded, no reset failure or timeout anywhere in the run.

Still shipping sgmii: all ports are 1G, LAN to LAN is switched in hardware, and SGMII is full duplex.

About the WiFi, that's right, shrinking some rings it could work, but the device it's a 256MB RAM so it needs to get more investigation about if it's stable or not. Anyways I got my MR80X working for like 10 days using my testing ath11k-smallbuffer without issues, but still checking...

And MAC we need to review deeper, because vendor image handles MAC in a different way.

I appreciate you being able to test the image, it could be useful for the PR.