← All Series
Installing OpenWrt on a Meraki MR18
15 parts · ~329 min total
March 24, 2026 · 9 min read
MerakiMR18RouterJTAGOpenWrt
This is my first blog—I am not very proficient in writing, especially not technical writing and my grammar sucks, but hey who cares. I will have a series of posts talking about how I managed to install OpenWrt on a Meraki MR18 access point. I will try to explain things as I go to not leave readers in the dark, but some background technical understanding/ability to research more in-depth confusing topics will greatly help the reader.
Read more →
March 24, 2026 · 23 min read
MerakiMR18RouterJTAGOpenWrt
I mean, where the fuck should I really even start?
I got hoes that I’m keepin’ in the dark
Okay, but really though, where do I start? Right now we know that the Meraki MR18 is cloud-locked by Cisco. Without an active Meraki dashboard license there are many niceties that I don’t have access to such as:
Read more →
March 24, 2026 · 19 min read
MerakiMR18RouterJTAGOpenWrt
Everything is wired up, let’s flash
She said, “Where we goin’?” I said, “The moon”
Alright so we left off with all the wires connected, the bench power supply ready, and my ESP-Prog looking like a spaghetti monster grew on my desk. Time to actually make this thing do something.
Read more →
March 24, 2026 · 31 min read
MerakiMR18RouterJTAGOpenWrt
The cache coherency nightmare and other fun MIPS things
I be going through some things, you don’t know what I’ve been thinkin'
So last post ended with the binary loaded, the CPU launched, and data error! on the serial console. The lzma-loader—the little decompressor that’s supposed to unpack our kernel—was choking on the LZMA stream. The binary I loaded was correct (I verified the MD5 before loading), the ath79 build was the right one, the address was correct. What the fuck.
Read more →
March 24, 2026 · 23 min read
MerakiMR18RouterJTAGOpenWrt
I know you’re somewhere out there, somewhere far away
Okay so the kernel booted. Like actually booted. I saw Linux version 6.6.73 scroll across the UART console. The CPU was running, the scheduler was active, idle task spinning. I set up my host Ethernet at 192.168.1.2/24, pointed my browser at 192.168.1.1, and… nothing. Ping? Nothing. Telnet? Nothing. ARP? Nothing.
Read more →
March 24, 2026 · 21 min read
MerakiMR18RouterJTAGOpenWrt
The last mile: a PHY bug, a 20-minute serial transfer, and victory
Crocodile leather patches, baby, oh (We made it on our own)
Last post ended with failsafe mode running but Ethernet RX completely broken. TX works—the MR18 sends ARP requests just fine—but it can’t receive a single packet. Every incoming frame triggers FCS errors. rx_packets is zero. Ping doesn’t work because ARP replies from the host never reach the device.
Read more →
March 25, 2026 · 34 min read
MerakiMR18MIPSAssemblyJTAGOpenWrtdeep-dive
Encoding instructions by hand like it’s 1985
They said “use a tool,” I said “nah I got this”
Post 4 mentioned that I hand-encoded every MIPS instruction as hex constants directly in Python. I figured it’s worth breaking down what that actually looks like in practice. So here’s the deep dive—every trampoline, every encoding decision, and the one-bit typo that almost ruined everything.
Read more →
March 25, 2026 · 15 min read
MerakiMR18PythonJTAGOpenWrtOpenOCDdeep-dive
The boring infrastructure that makes everything else possible
I got my mind on my money and my money on my mind
Every previous post in this series focused on the dramatic stuff—cache coherency nightmares, hand-encoded MIPS assembly, twenty-minute UART transfers. But none of that works without the plumbing. The mr18_flash.py script has about 300 lines of infrastructure code that handles three things: talking to a bench power supply, talking to OpenOCD over telnet, and managing the OpenOCD process itself. It’s not glamorous. It’s the kind of code you write at 1am because you’re tired of manually typing commands into four different terminals. Let’s walk through it.
Read more →
March 25, 2026 · 29 min read
MerakiMR18PythonJTAGOpenWrtEJTAGdeep-dive
Catching a CPU in the dark with a 1.5 second flashlight
I been up for a long time, I ain’t get no sleep for it
Post 3 gave the high-level overview of the timing attack—power on, wait 1.5 seconds, halt the CPU before Cisco’s kernel murders the JTAG interface. This post rips open mr18_flash.py and walks through every halt strategy and the main retry loop in detail. None of this is elegant. It’s the kind of code you write at 2am when you’ve already power-cycled an access point forty times and you’re starting to take it personally.
Read more →
March 25, 2026 · 21 min read
MerakiMR18PythonJTAGOpenWrtcachedeep-dive
What load_and_run() actually does (phases 0-2)
I been through the fire and the rain, and I came out the other side
Posts 3 and 4 gave the high-level overview of the load pipeline—flush, load, flush again, verify, launch. This deep-dive series rips open the actual Python and walks through the code. We’re starting with phases 0 through 2 of load_and_run(), which covers everything up to but not including the XOR verification passes.
Read more →
March 25, 2026 · 31 min read
MerakiMR18PythonJTAGOpenWrtMIPSdeep-dive
CPU-executed XOR verification
Trust none of what you hear and less of what you see / I had to verify the data runnin through the memory
Post 10 covered phases 0 through 2—flush, load, flush again. At that point we have 6.9 MB of initramfs sitting in DRAM, and now we need to answer a simple question: is it correct? This post covers phase 3, the XOR verification pipeline, including the approach that didn’t work and the one that does.
Read more →
March 25, 2026 · 15 min read
MerakiMR18PythonJTAGOpenWrtUARTdeep-dive
Launching the kernel and catching failsafe on the way down
Started from the bottom now we here, started from the bottom now my whole team here
Post 10 ended with a verified binary in clean DRAM. Post 5 gave the high-level failsafe saga. This post covers the actual code: the launch trampoline, the regression that taught me to stop touching the CPU, and two competing strategies for triggering failsafe mode.
Read more →
March 25, 2026 · 13 min read
MerakiMR18PythonOpenWrttelnetdeep-dive
Network setup and sysupgrade
Started from the bottom now we here, started from the bottom now my whole team here
Twelve posts deep. Cache flushes, MIPS trampolines, XOR verification, UART monitoring, failsafe timing. This post covers the last mile—once the kernel is booted and failsafe is confirmed, how do we talk to the device over the network and flash the permanent sysupgrade image? No more PRACC, no more cache paranoia. Just TCP/IP and a couple of file transfers.
Read more →
March 25, 2026 · 25 min read
MerakiMR18CMIPSAssemblyAR8035PHYdeep-dive
Writing a PHY driver without libc
I don’t need a feature, I just need a fix / raw register writes and a couple syscall tricks
Post 6 mentioned a standalone C program that pokes two MDIO registers to fix the AR8035 PHY’s broken RGMII RX clock delay. This deep dive rips open the actual source—ar8035_start.S, ar8035.c, and the Makefile—and walks through every layer from the assembly entry point down to the register writes that make Ethernet work. If you’ve ever wondered what bare-metal Linux userspace programming looks like on MIPS with zero library dependencies, this is it.
Read more →
March 25, 2026 · 27 min read
MerakiMR18PythonUARTOpenWrtdeep-dive
Hex-encoding 7 million bytes because the PHY won’t cooperate
Slow grind, patience on the line / twenty minutes on the wire just to make it mine
Post 6 described the hex-over-UART transfer protocol at a high level—hex-encode every byte, pipe it through an awk decoder on the MR18, pray nothing goes wrong for 20 minutes straight. This post rips open send_binary.py and uart_transfer.py and walks through the actual Python. Two scripts, two transfer sizes, same dumb protocol, very different engineering challenges.
Read more →