Router Emulator (MediaTek MT7981B / Filogic 820, MT7986 / Filogic 830, MT7987 / Filogic 850, MT7988 / Filogic 860 / 880, MT7621)
English · Русский · Build: Linux · Windows
Board presets are a separate archive since 3.6. The packages no longer contain
presets/. Every release hasRouter-Emulator-<version>-presets.zipnext to the packages (on GitHub, Google Drive and Yandex Disk): unpack it over the package. When only presets change, a presets-only releaseX.Y.Z(e.g. 3.6.1) brings just a new presets archive for the X.Y packages.
Download: Router Emulator on Google Drive
or on Yandex Disk: the packages of the
latest release, its presets archive (Router-Emulator-<version>-presets.zip)
and ready flash folders, one archive per folder
(Router-Emulator-<folder>.zip, e.g. Router-Emulator-nand-wr3000p.zip),
for all presets except Keenetic KN-1012 and Routerich AX3000 v1 (they need
dumps of your own router, which are not built). Unpack the presets archive
and the folders of the boards you need over a package.
A QEMU machine, mt7981-router, that emulates an MT7981B router board at
the hardware level. The board hardware (Ethernet PHYs/switch, flash, RAM
type and size, USB) is configurable, so one machine covers many devices;
board presets (presets/*.ini) describe concrete routers. Firmware
runs unmodified, through the same boot chain as on a real device:
BootROM (emulated) → BL2 (MediaTek preloader, DDR training) → BL31 (TF-A)
→ U-Boot → OpenWrt (kernel + rootfs from UBI on SPI-NAND)
Both OpenWrt's own bootloader ("ubootmod" layout) and vendor bootloaders (BL2/FIP dumped from real devices, with NMBM) boot. Everything the firmware needed was adapted on the emulator side; no firmware is patched.
MT7986A/B (Filogic 830) boards run on a second machine, mt7986-router
(preset key soc=mt7986): the same peripherals, four cores, the MT7986
BL2 with its own DRAM calibration and size detection, see
MT7986; MT7987A/B boards on mt7987-router
(soc=mt7987, see MT7987); MT7988A/D boards
(Filogic 880 / 860) on mt7988-router (soc=mt7988, see
MT7988); MT7621A boards (MIPS) on
mt7621-router in qemu-system-mipsel (soc=mt7621, see
MT7621).
Linux: README.build.linux.md (or the
Router-Emulator-<version>-linux-x86_64.tar.gz of a release: QEMU
with its libraries, no build needed; unpack the release's
Router-Emulator-<version>-presets.zip over it), then
tools/prepare-nand.sh cudy_wr3000p-v1 25.12.5 # official images -> nand-wr3000p/
./emulator.sh -P list # board presets
./emulator.sh -P cudy-wr3000p-v1 # router console in this terminalWindows: download the release zip (or build it, see
README.build.windows.md) and the release's
Router-Emulator-<version>-presets.zip, unpack both into the same place, run
emulator.exe. The release contains no NAND folders: create them with
tools/prepare-nand.sh (Linux or WSL) and copy them next to emulator.exe,
or use dumps of a real router.
64-bit Arm Linux (Raspberry Pi 4/5, Arm servers, TV boxes with CoreELEC /
LibreELEC): Router-Emulator-<version>-linux-aarch64.tar.gz. It brings its
own C library and a static bash, so it needs only an aarch64 kernel: it
also runs under a 32-bit userland (uname -m says aarch64 or armv8l;
a 32-bit ARMv7 kernel cannot run it: QEMU 10 emulates 64-bit guests on
64-bit hosts only). Build the flash folders on a PC and copy them over, then
./bash emulator.sh -P ... -w user -l user where bash is busybox. An
Amlogic S905 box (4x Cortex-A53, 1 GB) boots the WR3000S to LuCI in about
8 minutes.
-M mt7981-router,nand-dir=DIR,<options> (or mt7986-router) and
-m <RAM size> (128M, 256M, 512M, 1G or 2G; 128 MB is untested: there is
no BL2 for it to try, reports welcome):
| Option | Values | Meaning |
|---|---|---|
gmac0 |
mt7531 · an8855 · mt7988-switch · rtl8221b · yt8821 · gpy211 · en8811h · none |
what GMAC0 (mac@0, SGMII0) is wired to; an8855 = Airoha AN8855 switch at MDIO 1 (CPU port 5, e.g. Routerich BE7200); mt7988-switch = the MT7988's built-in 4-port switch; en8811h = Airoha EN8811H 2.5G PHY (OpenWrt One) |
ports |
e.g. wan:lan1:lan2:lan3:lan4 |
netdev ids of MT7531 ports 0..4, and port 5 with port5= (- = unused) |
port5, port5-phy-addr, port5-reset-gpio |
rtl8221b · yt8821 · gpy211 · none; MDIO address (5); GPIO |
a 2.5G PHY on MT7531 port 5 (SGMII), e.g. Netcore N60 Pro; its netdev is the 6th entry of ports |
gmac0-port, gmac1-port |
netdev id | port of a PHY attached directly to a GMAC |
gmac1 |
rtl8221b · yt8821 · gpy211 · en8811h · gphy · i2p5ge · mt7531-p5 · none |
GMAC1 (mac@1); gphy = MT7981 built-in 1G PHY; i2p5ge = MT7987 / MT7988 internal 2.5G PHY; gpy211 = MaxLinear GPY211C; mt7531-p5 = SGMII link to MT7531 port 5 (MediaTek reference design, e.g. SNR-CPE-AX2: its vendor firmware takes WAN through it; needs gmac0=mt7531 and no port5 PHY) |
gmac0-reset-gpio, gmac1-reset-gpio |
GPIO, -1 (default) |
hardware reset line of a 2.5G PHY; not wired by default (a missing line is harmless, a wrong one could hold the PHY in reset) |
gmac0-phy-addr, gmac1-phy-addr |
MDIO address | address of that PHY (default 1 on gmac0, 6 on gmac1) |
flash |
nand · nor · emmc |
boot flash: SPI-NAND on SPI0 (default), SPI-NOR on SPI2 (MT7986: SPI0) or eMMC (see below) |
nor-nand |
on |
a SPI-NOR and a SPI-NAND (Teltonika RUTC50, OpenWrt One): the flash folder holds the NOR partition files, its subfolder nand the NAND ones; flash=nor / flash=nand picks the boot flash (see SPI-NOR + SPI-NAND) |
nand |
128 · 256 · 512 |
W25N01GV / W25N02KV / W25N04KV SPI-NAND |
nand-id |
2 to 8 bytes in hex | the bytes the SPI-NAND answers to READ ID, repeating after that (default the Winbond ID, e.g. efaa21); c8017f7f7f = ESMT F50L1G41LB, the chip of e.g. the Cudy WR3000P / WR3000E. The chip still works as the emulated one; vendor firmware may check the ID (MT7621: the parallel NAND's READ ID, see below) |
nand-spi |
0 (default) · 1 · 2 |
SPI controller of the SPI-NAND (BPI-R4 Lite: 2) |
switch-irq-gpio |
GPIO | EINT of the MT7531 interrupt (default 38 / 66 / 41 for MT7981 / MT7986 / MT7987) |
flash=emmc, emmc-boot |
MB (4) | eMMC on MSDC0 instead: one image (the first *.img of the flash folder, or -drive if=sd) = boot0 + boot1 (emmc-boot MB each) + user area, size a power of 2 |
nor, nor-id |
MB (16), JEDEC ID (ef4018) |
SPI-NOR size and ID, e.g. 204018 = XMC XM25QH128C/D, c84018 = GD25Q128; nor=32,nor-id=ef4019 = Winbond W25Q256JV, nor=64,nor-id=ef4020 = Winbond W25Q512JV (over 16 MB: 4-byte addressing; e.g. a WR3000 with a bigger chip). MT7621: 16, 32 or 64 MB, and without nor-id the Winbond chip of that size |
nor-uid |
16 hex digits | MT7621 only: SPI-NOR unique ID (command 4b, default none); stock Cudy firmware of NOR boards (M1300 v2) checks chip ID + unique ID (/proc/sys/dev/flash_uuid, e.g. 2040182040- + 16 digits) against bdinfo, without a match no admin password can be set or checked: set nor-id to the chip's ID and nor-uid= the 16 digits after the - |
ddr |
ddr3 · ddr4 |
soldered DRAM type: a BL2 built for the other type stops the machine, as DRAM init fails on a real board |
usb-port |
none · 2 · 3 |
USB connector (USB 3.0: devices attach at SuperSpeed) |
usb-host |
VID:PID[;VID:PID…] |
pass USB devices of this PC through to the router (USB 2.0 root ports 2, 3, …, also on boards without a connector), e.g. a Wi-Fi dongle, an LTE modem, a USB Ethernet adapter; see "USB Wi-Fi dongle" |
pcie-wifi |
none · mt7992 |
MT7987 / MT7988: card in the PCIe slot. mt7992 = MediaTek MT7992 Wi-Fi 7 (2.4 + 5 GHz); OpenWrt drives it with kmod-mt7996e and the firmware of kmod-mt7992-firmware, which official MT7987 images do not include |
reset-gpio, wps-gpio |
GPIO | buttons (QOM /machine/pinctrl reset-button, wps-button) |
reset-active-high, wps-active-high |
on |
button reads 1 when pressed (default: active low) |
reset-hold |
ms | power on with reset held (U-Boot TFTP recovery) |
pan |
on (default) · off |
report PAN (Privileged Access Never) in the CPU ID registers. The A53 / A73 cores have none, so OpenWrt's kernel emulates it by switching TTBR0 on every system call, and under TCG each switch flushes the TLB; with pan=on the kernel uses the PAN that QEMU implements anyway, system calls get 2.5–3× faster and booting takes ~40 % less host CPU. off: the cores' own ID registers |
poweroff |
stop · reboot |
stop (default): the emulator ends when Linux powers off ("reboot: Power down" on UART0); reboot: like a real board, whose firmware cannot power down |
gpio-log |
on |
print GPIO output changes and the LEDs' states |
led1 … led32 |
NAME: COLOUR SOURCE [+ COLOUR SOURCE…] |
front panel LEDs (see below); led-state (QOM /machine) returns their states |
efuse |
file | load the eFuse contents from a dump of a real board (up to 4 KiB, as read from /sys/bus/nvmem/devices/nvmem0/nvmem), so calibration and chip data match that board |
efuse-uid |
32 hex digits | set the per-chip unique block, so several emulated boards are not identical |
nand-uid |
32 hex digits | SPI-NAND unique ID (default: derived from the flash folder name); stock Cudy firmware checks chip ID + unique ID (/proc/sys/dev/flash_uuid) against fuuid in bdinfo (C801- + the unique ID + its complement), without a match login is disabled ("Invalid board info!"): set nand-id=c8017f7f7f and nand-uid= the 32 hex digits after C801- |
Front panel LEDs: led1=Status: red gpio:11:low + white gpio:10:low is
one lamp with two colours, each on its own GPIO (:low = GPIO_ACTIVE_LOW).
Sources: gpio:N[:low] (SoC GPIO), phy:ADDR:N (LED pin N of the
Ethernet PHY at MDIO address ADDR, as its LED registers program it:
RTL8221B, GPY211; e.g. the WR3000P WAN lamp in current OpenWrt),
swphy:PORT:N (LED pin N of the switch's PHY of port PORT: MT7988
built-in switch, e.g. the BPi-R4 port LEDs), ws2812:BUS:N (WS2812B RGB
chain on SPI bus BUS, colour rgb: Redmi
AX6000), hc595:BUS:N (output N of a 74HC595 shift register chain on SPI
bus BUS, Linux gpio-74x164: the Teltonika RUTC50 mobile, SIM and signal
LEDs) and pwm:N (PWM channel, pwm-leds: BPi-R4 Lite, OpenWrt One). QEMU samples them
every 50 ms; qom-get /machine led-state gives per LED (;) and colour
(,) 0, 1, b (blinking) or #rrggbb / b#rrggbb. The Windows
launcher shows them as a row of lamps above the console; with -g
(gpio-log=on) QEMU prints every change ("LED Status: white on").
tools/dts-leds.py fills the led keys of the
presets from the OpenWrt device tree (--write --all).
Network ports are QEMU netdevs with the ids used above (wan, lan1, …).
Launcher-only preset keys: lan-ip (router LAN address, default
192.168.1.1) and lan-forwards (default 8080:80,8443:443,8022:22, PC
port:router port) for the LAN port "this PC only": a QEMU user-mode network in the
router's /24 with restrict=on and these forwards from 127.0.0.1. QEMU's
own DHCP server is off there (dhcp=off, needs libslirp ≥ 4.7): OpenWrt's
dnsmasq does not serve DHCP on br-lan while another server answers.
A preset is an INI file: name, description, soc (mt7981 default,
mt7986), ram (MB), nand-dir,
build-only keys (openwrt= OpenWrt profile, openwrt-local= own build,
openwrt-stock= vendor bootloader dumps) and machine options. Both
launchers use them; the Windows launcher has an editor for them (Ethernet,
switch port labels, RAM type/size, boot flash and a second flash
(SPI-NOR + SPI-NAND, nor-nand), USB, LEDs (GPIO, PHY, WS2812B, 74HC595,
PWM), button GPIOs with an
"active high" tick — unticked = active low, the default and what most
routers use; saving from the editor drops ; comments).
| Preset | Ethernet | RAM | NAND | USB | Bootloader |
|---|---|---|---|---|---|
| Cudy WR3000P v1 | 2.5G WAN RTL8221B + 4×1G MT7531 | DDR4 512 MB | 128 MB (ESMT F50L1G41LB) | 2.0 | OpenWrt (U-Boot layout) |
| Cudy WR3000H v1 | 2.5G WAN RTL8221B + 4×1G MT7531 | DDR3 256 MB | 128 MB | – | OpenWrt (U-Boot layout) |
| Cudy WR3000S v1, WR3000E v1 | 5×1G MT7531 (WAN = port 0) | DDR3 256 MB | 128 MB | – | OpenWrt (U-Boot layout) |
| Cudy WBR3000UAX v1 | 5×1G MT7531 (WAN = port 0) | DDR3 512 MB | 128 MB | 3.0 | OpenWrt (U-Boot layout) |
| Cudy WR3000U v1 | 5×1G MT7531 (WAN = port 0) | DDR3 512 MB | 256 MB | 3.0 | vendor (NMBM) |
| Cudy TR3000 v1 | 2.5G WAN RTL8221B on GMAC0 + 1G LAN built-in PHY | DDR3 512 MB | 128 MB | 3.0 | OpenWrt (U-Boot layout) |
| Cudy TR3000 256MB v1 | same, no switch | DDR3 512 MB | 256 MB | 3.0 | vendor (NMBM) |
| Bazis AX3000WM / WD-R3000N-G2A | 2.5G WAN MaxLinear GPY211C on GMAC0 + 1G LAN built-in PHY, no switch | DDR3 256 MB | 128 MB, no bdinfo | 3.0 | OpenWrt |
| Cudy M3000 v1 / v2 (RTL8221B) | 2.5G WAN RTL8221B + 1G LAN built-in PHY | DDR3 256 MB | 128 MB | – | OpenWrt (own build, U-Boot layout) |
| Cudy M3000 v2 (YT8821) | 2.5G WAN Motorcomm YT8821 + 1G LAN built-in PHY | DDR3 256 MB | 128 MB | – | OpenWrt (own build, U-Boot layout) |
| Netis NX30 V2, NX31 | 1G WAN built-in PHY + 3×1G LAN MT7531 | DDR3 256 MB | 128 MB | – | OpenWrt |
| Netis NX32U | 4×1G MT7531 (WAN = port 0) | DDR3 256 MB | 128 MB | 3.0 | OpenWrt |
| Cudy WR3000 v1 | 4×1G MT7531 (WAN = port 0) | DDR3 256 MB | SPI-NOR 16 MB (XM25QH128C) | – | vendor |
| Xiaomi Mi Router AX3000T | 4×1G MT7531 (WAN = port 0) | DDR3 256 MB | 128 MB, Xiaomi layout | – | OpenWrt (U-Boot layout) |
| SNR-CPE-AX2 | 5×1G MT7531 (WAN = port 0); GMAC1 linked to switch port 5 (WAN of the SNR firmware) | DDR3 256 MB | 128 MB, no bdinfo | – | OpenWrt; the SNR firmware boots from a dump of your own router's whole NAND (not provided) |
| Huasifei WH3000 Pro NAND | 2.5G LAN RTL8221B + 1G WAN built-in PHY, no switch | DDR4 1 GB | 256 MB, no bdinfo | 3.0 | vendor (NMBM) |
| Huasifei WH3000R NAND | 1G WAN built-in PHY + 3×1G LAN MT7531 | DDR3 512 MB | 256 MB, no bdinfo | 3.0 | vendor (NMBM) |
| Keenetic KN-1012 | 1G WAN built-in PHY + 4×1G LAN MT7531 (SFP not emulated) | DDR4 512 MB | 256 MB | 3.0 | vendor (NMBM) |
| Xiaomi Redmi AX6000 (MT7986A) | 4×1G MT7531 (WAN = port 4) | DDR4 512 MB | 128 MB | – | OpenWrt (U-Boot layout) |
| Netcore N60 (MT7986A) | 2.5G WAN RTL8221B + 4×1G MT7531 | DDR3 256 MB | 128 MB | – | OpenWrt |
| Netcore N60 Pro (MT7986A) | 2.5G WAN GPY211 + 2.5G LAN GPY211 on switch port 5 + 3×1G MT7531 | DDR4 512 MB | 128 MB | 3.0 | OpenWrt |
| Bananapi BPi-R4 Lite (MT7987A) | 2.5G WAN internal PHY + 4×1G MT7531 (SFP cage not emulated); Wi-Fi 7 MT7992 card on PCIe — for Wi-Fi install kmod-mt7996e kmod-mt7992-firmware |
DDR4 2 GB | 256 MB on SPI2, FIP in UBI | 3.0 | OpenWrt |
| GL.iNet GL-MT6000 (MT7986A) | 2.5G WAN RTL8221B + 2.5G LAN RTL8221B on switch port 5 + 4×1G MT7531 | DDR4 1 GB | eMMC | 3.0 | OpenWrt |
| Huasifei WH3000 Pro eMMC | 2.5G LAN RTL8221B + 1G WAN built-in PHY, no switch | DDR4 1 GB | eMMC, vendor layout | 3.0 | OpenWrt (RAX3000M eMMC DDR4 build) |
| Routerich AX3000 | 3×1G LAN + WAN MT7531 (WAN = port 4) | DDR3 256 MB | 128 MB, no bdinfo | 2.0 | OpenWrt (U-Boot layout) |
| Routerich AX3000 v1 | 1G WAN built-in PHY + 3×1G LAN MT7531 | DDR3 512 MB | 128 MB, no bdinfo | 3.0 | vendor (own dumps needed) |
| Routerich BE7200 (MT7987A) | 2.5G WAN internal PHY + 3×1G LAN Airoha AN8855; Wi-Fi 7 MT7992 on PCIe | DDR4 1 GB | 512 MB (W25N04KV) | 3.0 | OpenWrt |
| Asus ZenWiFi BT8 (MT7988D, Filogic 860) | 2.5G WAN internal PHY + 2×1G LAN built-in switch (lan3 on GMAC2 and the MT7996 Wi-Fi not emulated) | DDR4 1 GB | 128 MB, FIP and Factory in UBI | 3.0 | OpenWrt (U-Boot layout) |
| TP-Link TL-7DR7230 v1 (MT7988D, Filogic 860) | 2.5G WAN internal PHY + 4×1G LAN built-in switch | DDR4 512 MB | 128 MB, TP-Link layout | – | OpenWrt snapshot (no LuCI) |
| Bananapi BPi-R4 (MT7988A, Filogic 880) | WAN + 3×1G LAN built-in switch (SFP+ cages not emulated) | DDR4 2 GB (real: 4 GB) | eMMC (bpi-r4 layout) |
3.0 | OpenWrt |
| Teltonika RUTC50 | 1G WAN built-in PHY + 4×1G LAN MT7531 (the mobile modem is not emulated); LEDs on a 74HC595 | DDR3 512 MB | SPI-NOR 16 MB boot + SPI-NAND 512 MB (rutos-a / rutos-b) | 2.0 | OpenWrt RFB BL2 + own U-Boot (tools/uboot-rutc50) |
| OpenWrt One | 2.5G WAN Airoha EN8811H + 1G LAN built-in PHY | DDR4 1 GB | SPI-NAND 256 MB boot + SPI-NOR 16 MB recovery | 2.0 | OpenWrt |
| Zbtlink ZBT-WG3526 (16M) (MT7621A, MIPS) | 5×1G MT7530 (WAN = port 4); Wi-Fi MT7603E + MT7612E on PCIe (radio silent) | DDR3 512 MB (448 MB usable) | SPI-NOR 16 MB | 3.0 | OpenWrt |
| Xiaomi Mi Router 3G (stock U-Boot) (MT7621A, MIPS) | 1G WAN + 2×1G LAN MT7530; Wi-Fi MT7603E + MT7612E on PCIe (radio silent) | DDR3 256 MB | parallel NAND 128 MB (Xiaomi layout) | 3.0 | stock Xiaomi U-Boot 1.1.3 + OpenWrt |
| Xiaomi Mi Router 3G (Breed) (MT7621A, MIPS) | as above | DDR3 256 MB | parallel NAND 128 MB (Xiaomi layout) | 3.0 | Breed 1.2 + OpenWrt |
| Xiaomi Mi Router 4 (stock U-Boot) (MT7621A, MIPS) | 1G WAN (MT7530 PHY 4 muxed to GMAC1) + 2×1G LAN MT7530; Wi-Fi MT7603E + MT7612E on PCIe (radio silent) | DDR3 128 MB | parallel NAND 128 MB (Xiaomi layout) | – | stock Xiaomi U-Boot 1.1.3 + OpenWrt |
| Cudy WR1300S (MT7621A, MIPS) | 1G WAN (MT7530 PHY 0 muxed to GMAC1) + 4×1G LAN MT7530; Wi-Fi MT7603E + MT7663E (MT7613BE) on PCIe (radio silent) | DDR3 128 MB | SPI-NOR 16 MB | 3.0 | stock Cudy U-Boot 1.1.3 + OpenWrt 25.12.5 (third-party build, the board is not in OpenWrt yet) |
| Cudy M1300 v2 (MT7621A, MIPS) | 1G WAN (MT7530 PHY 4 muxed to GMAC1) + 1G LAN MT7530 port 3; Wi-Fi MT7603E + MT7663E (MT7613BE) on PCIe (radio silent) | DDR3 128 MB | SPI-NOR 16 MB | – | stock Cudy U-Boot 1.1.3 + OpenWrt; the stock Cudy firmware (LEDE, kernel 4.4) from a dump of your own router's flash |
Netis boards have no bdinfo partition (FIP at 0x380000, ubi at 0x580000;
MACs in Factory): presets set openwrt-no-bdinfo=1. The MT7986 boards use
the same layout. Other layouts are given as openwrt-parts=label:size,...
(tools/prepare-nand.sh --parts), e.g. the Xiaomi AX3000T: BL2, Nvram,
Bdata, Factory, FIP, crash, crash_log, ubi at 0x600000, KF. Its firmware
calls the LAN ports lan2..lan4; the preset's ports names the emulator's
connections, so the launcher's LAN (lan1) is switch port 1.
mt7986-router is the same machine with the MT7986 differences: four
Cortex-A53 cores, HW_CODE 0x7986, pinctrl at 0x1001f000 (101 GPIOs),
eFuse at 0x11d00000, two SPI controllers, no built-in GbE PHY, the MT7531
interrupt on EINT 66. Its BL2 (MediaTek's prebuilt DRAM library, DDR3 and
DDR4) calibrates against the same DRAM controller model, and detects the
DRAM size with the controller's test agent (writes beyond -m wrap
around, as on the real chip). The Wi-Fi driver reads back one MT7976
a-die (2.4 + 5 GHz); OpenWrt has no default EEPROM for the MT7986, so
tools/prepare-nand.sh --soc mt7986 writes a minimal one into an empty
Factory (with a Factory dump of a real board its calibration is used).
tools/prepare-nand.sh --soc mt7986 --no-bdinfo netcore_n60-pro 25.12.5 nand-n60-pro
./emulator.sh -P netcore-n60-promt7987-router: UARTs at 0x11000000, SPI0..2 at 0x11007800 /
0x11008800 / 0x11009800, TOP_MISC at 0x10021000, eFuse at 0x11d30000,
NETSYS v3 frame engine (PDMA at 0x6800), the internal 2.5G PHY
(gmac1=i2p5ge, MDIO 15; firmware loading and link), LVTS thermal
sensor, boot strap register (DDR type for "comb" BL2s, boot media for
U-Boot's rootdisk choice). The MT7987 BL2 calibrates DRAM against the
same controller model. Options for its boards: nand-spi=2 (SPI-NAND on
SPI2), switch-irq-gpio=41, -m 2G.
Wi-Fi is on PCIe: the machine has the Gen3 controller of port 0
(mtk_pcie.c: link, configuration TLPs, INTx, MSI capture; QEMU's
root port is the RC) and, with pcie-wifi=mt7992, an MT7992 Wi-Fi 7
card in the slot (mt7992_wifi.c, radio silent like the built-in Wi-Fi
of the other SoCs: the firmware is downloaded and answered, both bands
come up, hostapd runs, nothing is on the air). The official image has no
driver for it; install it once (WAN must reach the Internet, the
packages stay in the overlay):
apk update && apk add kmod-mt7996e kmod-mt7992-firmware
rebootThen enable radio0 / radio1 and their networks in LuCI (Wi-Fi is off
by default in OpenWrt).
tools/prepare-nand.sh --soc mt7987 --flash-mb 256 --ubi-fip bananapi_bpi-r4-lite 25.12.5
./emulator.sh -P bananapi-bpi-r4-liteeMMC boards (GL.iNet GL-MT6000) use flash=emmc: the MSDC host model
serves TF-A and U-Boot (PIO) and Linux (descriptor DMA); BL2 boots from
eMMC boot partition 0 ("EMMC_BOOT"), the FIP, kernel and rootfs are GPT
partitions. tools/prepare-nand.sh --soc mt7986 --emmc glinet_gl-mt6000 25.12.5 builds the image with tools/mkemmc.py
(sparse, 1 GB).
The Huasifei WH3000 Pro eMMC (MT7981B) keeps its vendor GPT layout
(u-boot-env, factory, fip, config, kernel, rootfs; --emmc-layout wh3000-pro), and OpenWrt builds only its firmware (sysupgrade.bin, a tar
with the kernel FIT and the root squashfs), not its bootloader. The preset
(openwrt-uboot=, --uboot) takes the OpenWrt BL2 and FIP of the CMCC
RAX3000M eMMC DDR4 (same SoC, boot medium and DRAM type) and writes a
U-Boot environment that boots the FIT in the kernel partition; the
kernel finds root=PARTLABEL=rootfs through its own device tree, as with
the vendor bootloader. With dumps of the vendor BL2 (*boot0*.bin) and FIP
(*fip*.bin), --stock DIR keeps the vendor bootloader instead:
tools/prepare-nand.sh --emmc --emmc-layout wh3000-pro --uboot cmcc_rax3000m-emmc-ddr4 huasifei_wh3000-pro-emmc 25.12.5 emmc-wh3000-pro
./emulator.sh -P huasifei-wh3000-pro-emmcmt7988-router: the MT7988A (Filogic 880, 4 cores) and the MT7988D
(Filogic 860: the same die, the board DT disables cpu@3). Four Cortex-A73
cores (TF-A for the MT7988 carries only Cortex-A73 code; QEMU's
cortex-a73 is the A72 model with the A73 IDs), MCUCFG at 0x100e0000,
UARTs at 0x11000000, SPI0..2 at 0x11007000 / 8000 / 9000, eFuse at
0x11f50000, NETSYS v3 (PDMA at 0x6800), the internal 2.5G PHY
(gmac1=i2p5ge, MDIO 15, MT7988 firmware), the built-in switch
(gmac0=mt7988-switch: 4 ports, its GbE PHYs with eFuse calibration and
LED registers), xHCI ssusb1 (USB 3.0), MSDC (eMMC), PCIe (pcie-wifi=),
two LVTS controllers and the boot strap register U-Boot picks
/chosen/rootdisk-<media> by. The official BL2 (also the DDR3/DDR4
"comb" ones) calibrates DRAM against the same controller model.
Not emulated: GMAC2 / the 10G USXGMII ports (SFP+ cages, the BT8's lan3),
the SNFI NAND controller (snand BL2s: the BPi-R4 runs from eMMC here),
the RT5190A PMIC (cpufreq stays at the boot clock), MT7996 Wi-Fi cards.
tools/prepare-nand.sh --soc mt7988 --ubi-fip --ubi-factory 0x200000 asus_zenwifi-bt8 25.12.5
./emulator.sh -P asus-zenwifi-bt8
tools/prepare-nand.sh --soc mt7988 --emmc --emmc-layout bpi-r4 bananapi_bpi-r4 25.12.5
./emulator.sh -P bananapi-bpi-r4The Asus ZenWiFi BT8 keeps its MACs and Wi-Fi EEPROM in a UBI volume
factory (--ubi-factory SIZE, preset key openwrt-ubi-factory=). The
BPi-R4's eMMC image follows OpenWrt's emmc-gpt.bin (ubootenv, factory,
fip, recovery, production; the sysupgrade FIT goes into production).
The TP-Link TL-7DR7230 v1 exists in OpenWrt snapshot only: its preset
has openwrt-version=snapshot (snapshot images have no LuCI; SSH works).
With nor-nand=on a board has both flashes: the SPI-NOR on SPI2 and the
SPI-NAND on SPI0 (nand-spi). The flash folder holds the NOR partition
files and its subfolder nand the NAND ones (same *mtdN* naming, each
flash on its own); flash=nor or flash=nand says which one the BootROM
boots from — the boot switch of the OpenWrt One.
nor-rutc50/mt7981.mtd0.BL2.bin … mt7981.mtd8.recovery.bin SPI-NOR 16 MB
nor-rutc50/nand/mt7981.mtd0.rutos-a.bin, mt7981.mtd1.rutos-b.bin SPI-NAND 512 MB
- Teltonika RUTC50: boots from the NOR (BL2, u-boot-env, factory,
config with the MACs, FIP, bootconfig-a/b, event-log, recovery); the
system is UBI (kernel, rootfs, rootfs_data) in the NAND partition
rutos-a. OpenWrt has no bootloader for it and OpenWrt's MT7981 SPI-NOR U-Boot builds cannot read UBI (RFB) or boot only the NOR (OpenWrt One), so the preset uses the OpenWrt MT7981 RFB SPI-NOR BL2 and a U-Boot built bytools/uboot-rutc50/build.sh(OpenWrt's U-Boot sources and patches, the RFB SPI-NOR config with UBI, the RUTC50 flash layout; the FIP in that folder is the result). Its environment bootsrutos-awithubi.mtd=rutos-a, as the Teltonika bootloader does, so OpenWrt's sysupgrade and bootcount scripts work. The mobile, SIM and signal LEDs are on a 74HC595 on SPI1 (hc595:1:N). - OpenWrt One: the official
snand-factory.bin(BL2 + UBI with FIP, ubootenv, recovery, production) andnor-factory.bin(recovery BL2, Factory with the Wi-Fi EEPROM, FIP and the recovery system). The preset boots the NAND;-o flash=noris the boot switch at NOR (recovery U-Boot and system). The 2.5G WAN PHY is an Airoha EN8811H (gmac0=en8811h): Linux loads its MCU firmware over MDIO, then the link comes up at 2.5 Gbit/s.
tools/prepare-nand.sh --nor-nand rutc50 teltonika_rutc50 25.12.5
./emulator.sh -P teltonika-rutc50
tools/prepare-nand.sh --nor-nand openwrt-one openwrt_one 25.12.5
./emulator.sh -P openwrt-one # -o flash=nor: recovery from the NORMediaTek MT7621A boards run on a second QEMU binary,
qemu-system-mipsel, machine mt7621-router (preset key soc=mt7621;
emulator.sh, the launcher and tests/quick.py pick the binary). The
boot chain is the real one: the CPU starts in the SPI-NOR mapped at
0x1fc00000; U-Boot's TPL locks the L2 cache and loads the SPL into it,
the SPL runs MediaTek's DDR init blob in the Frame Engine SRAM, then U-Boot
and OpenWrt; NAND boards boot through the NAND BootROM (stage1 with the DDR
init into the locked L2, then the bootloader). Emulated: MIPS 1004Kc, 2 cores × 2 VPEs (MT ASE: 4 CPUs in Linux, chip ID
MT7621A; -smp 2: one core, MT7621S; -smp 1), CM/GIC/CPC (cores
powered up by the CPC, VPEs by MT ASE code; the first VPE's CP0 timer on its IP7, the
others' via the GIC), DRAM 128/256/512 MB (above 448 MB at 0x20000000, as on the
SoC), SPI controller + SPI-NOR, UART, Frame Engine (QDMA/PDMA, NETSYS v1)
with the MT7530 switch (MCM), xHCI (USB 3.0 + 2.0), GPIO with buttons and
LEDs, the PCIe host (three root ports, pcie0..2=CARD) with "radio
silent" Wi-Fi cards for the unmodified mt76 drivers: mt7603e (MT7603E,
2.4 GHz), mt7612e (MT7612E, 5 GHz) and mt7663e (MT7663E / MT7613BE,
5 GHz, driver mt7615e: ROM patch and N9 firmware) load their firmware, hostapd
runs, nothing goes on the air; a card without an EEPROM in flash gets a
minimal one from its eFuse with the MAC 80:af:ca:11:22:35 + slot; the
parallel NAND (NFI + ECC, PIO, flash=nand, nand-id=); the DRAMC's
software test engine, so a vendor stage1 that calibrates the DDR itself
(stock Xiaomi U-Boot) finds its pass windows; poweroff=stop|halt
(Linux's poweroff / halt end the emulator, or it stays halted like the
chip, which cannot switch itself off); the write-back L1 data cache as far
as uncached stores go (dcache=on, the default: Breed overruns an
uncached memset into a malloc header and relies on the dirty cached copy;
on from reset until the OS runs user code, so OpenWrt runs at full
speed). Not yet: SD card.
tools/prepare-nand.sh --soc mt7621 zbtlink_zbt-wg3526-16m 25.12.5 # nor-zbt-wg3526-16m
./emulator.sh -P zbtlink-zbt-wg3526-16mThe flash folder holds mt7621.mtd0.u-boot.bin (the OpenWrt U-Boot build
u-boot-mt7621_zbtlink_zbt-wg3526-16m/u-boot-mt7621.bin), u-boot-env,
factory (MAC 80:af:ca:11:22:33 at 0xe000: LAN …:33, WAN …:34) and
firmware (the sysupgrade.bin). The OpenWrt image has no
kmod-usb-storage: for a USB stick install it (apk add kmod-usb-storage).
Xiaomi Mi Router 3G (two presets, one per bootloader): OpenWrt has no
bootloader of its own for it, so the flash folder is built from a dump of
the router's Bootloader partition (stock U-Boot or Breed) and, if there,
its factory (the Wi-Fi calibration; the MACs are replaced by
80:af:ca:11:22:33 and up: WAN …:33, LAN …:34, Wi-Fi …:35 / …:36):
# DIR: *mtd0*.bin or *Bootloader*.bin (+ *factory*.bin)
tools/prepare-nand.sh --soc mt7621 --stock DIR xiaomi_mi-router-3g 25.12.5 nand-mi-router-3g-uboot
./emulator.sh -P xiaomi-mi-router-3g-uboot # or -breedXiaomi Mi Router 4 is built the same way (xiaomi_mi-router-4, folder
nand-mi-router-4, preset xiaomi-mi-router-4); its WAN is the MT7530's
PHY 4 wired through port 5 straight to GMAC1 (PHY muxing), which the
emulator follows from the switch's MTRAP register. Only Bootloader and
factory are needed: Config (the stock U-Boot's environment) and
Bdata hold the router's serial number, MAC and passwords, leave them out.
Cudy WR1300S (SPI-NOR, stock Cudy U-Boot 1.1.3): not in OpenWrt yet
(PR #22365), so the folder
takes an OpenWrt 25.12.5 build for it. The stock U-Boot reads the kernel
through the flash's memory-mapped window at 0x1c000000 and talks to the
SPI controller with automatic chip select, both emulated. Its bdinfo holds
the board data (encrypted) and the MAC: it is generated (MAC
80:af:ca:11:22:33), only u-boot and factory come from the router:
# DIR: *mtd0*.bin or *u-boot*.bin, *factory*.bin, the *sysupgrade.bin
tools/prepare-nand.sh --soc mt7621 --nor --stock DIR cudy_wr1300s 25.12.5 nor-wr1300s
./emulator.sh -P cudy-wr1300sCudy M1300 v2 is in OpenWrt (cudy_m1300-v2, folder nor-m1300-v2)
and is built the same way, from its u-boot and factory. Its two ports
are auto-sensing in the stock firmware; OpenWrt makes switch port 3 the LAN
and PHY 4 (through port 5 to GMAC1) the WAN. The stock Cudy firmware (LEDE,
kernel 4.4, MediaTek's vendor Wi-Fi drivers) also runs: split a dump of the
whole 16 MB flash at 0x30000 / 0x40000 / 0x50000 / 0xff0000 into
mt7621.mtd0.u-boot.bin ... mt7621.mtd4.bdinfo.bin of a folder and pass
it with -n. Its LAN address is 192.168.10.1 (-o lan-ip=192.168.10.1
with -l user), and its console has no shell. The preset has the board's
NOR chip (nor-id=204018, XMC XM25QH128D). The stock firmware compares the
chip's unique ID with bdinfo: without your router's ID no admin password
can be created or checked ("Invalid password!"). Give it as nor-uid, the
16 digits after the - of cat /proc/sys/dev/flash_uuid on the router:
-o lan-ip=192.168.10.1,nor-uid=0123456789ABCDEF.
The folder (tools/mknand.py mt7621-nand) holds the stock partitions,
OpenWrt's kernel1.bin in kernel (0x600000, "System 2") and
rootfs0.bin in ubi; kernel_stock (System 1, the stock firmware's
kernel) stays erased, so the stock U-Boot falls back to System 2 as on a
router with OpenWrt; Breed boots that kernel too. The MediaTek U-Boot
keeps a factory bad block table in the last 4 blocks, outside every
partition (written at the first boot, so partition dumps lack it; without
it the stock U-Boot scans the used flash for bad block marks, takes data
for marks and skips the kernel): when no dump holds those blocks, the
emulator puts an all-good table there.
Any dump X may come with an X.oob (its spare area, 64 bytes a page).
All device models live in hw/arm/filogic/ of the QEMU tree
(patches: qemu-patches/, base QEMU v10.1.0).
| Block | Model | Notes |
|---|---|---|
| CPU, GIC | 2× (MT7986, MT7987: 4×) Cortex-A53, MT7988: 4× Cortex-A73 (QEMU's cortex-a73: the A72 model with the A73 IDs and CPUACTLR_EL1) (EL3/EL2), GICv3, arch timer 13 MHz | secondary CPUs started by BL31 through TOPMISC SPMC power-on; PSCI is BL31's |
| BootROM | high-level emulation (filogic_router.c) |
parses the SPINAND! header + GFH FILE_INFO, loads BL2 into L2 SRAM, jumps at EL3 |
| DRAM controller | mtk_sysctl.c + generated status table |
broadcast mode, RTSWCMD/MRW responses, jitter meter, DQS gating lead/lag, RX data eye: MediaTek's binary DRAM calibration finds real windows, BL2 log is clean (DDR3 and DDR4); DDRCOMMON0 DDR3EN/DDR4EN checked against ddr= |
| Clocks, power, misc | mtk_sysctl.c |
sparse register file + special cases (frequency meter, CPU power-on, TRNG v2, eFuse calibration data, IPPC, thermal sensor ≈45 °C, EIP-97 ID, WED reset bits) |
| APXGPT | mtk_sysctl.c |
8 general purpose timers (used by vendor BL2s) |
| TOPRGU | mtk_toprgu.c |
watchdog with real timeout, SW reset (reboot), reset status kept across reset |
| UART ×3 | QEMU 16550 + MTK extra registers | |
| SPI (IPM) | mtk_spim.c |
FIFO + DMA, half-duplex spi-mem mode used by TF-A/U-Boot/Linux |
| SPI-NAND | spinand.c |
W25N01GV (2048+64) / W25N02KV (2048+128) / W25N04KV (512 MB, 2048+128, two planes), on-die ECC, ONFI parameter page, unique ID page; backing store = folder of partition dumps (see below) or one raw image with OOB |
| eMMC | mtk_msdc.c + QEMU eMMC |
MSDC host: commands, auto CMD23/CMD12, PIO FIFO (TF-A, U-Boot), GPD/BD DMA (Linux); card with boot0/boot1 and user area; BootROM boots EMMC_BOOT images from boot0 |
| SPI-NOR | spinor.c |
3-byte addressing (up to 16 MB), selectable JEDEC ID, reads 1-1-1/1-1-2/1-1-4/1-2-2/1-4-4, page program, 4K/32K/64K/chip erase, status registers with QE; BootROM boots SF_BOOT images from it; same folder backing store; with nor-nand=on next to a SPI-NAND |
| Ethernet | mtk_eth.c |
frame engine: QDMA TX (Linux), PDMA RX, PDMA v2 (U-Boot); TSO + checksum offload; LynxI SGMII PCS ×2; any combination of switch / PHYs on the two GMACs; interrupt routing of mainline Linux and of MediaTek's SDK driver (vendor firmware: FE_INT_GRP groups, PDMA RX on the pdma lines, GIC 189..192) |
| Switch | mtk_eth.c |
MT7531: paged MDIO access, internal PHY indirect access, MTK special tag (DSA), learning FDB, port matrix, VLAN table (MediaTek's SDK switch driver), port 5 to a PHY or to GMAC1, link IRQ → EINT 38. Airoha AN8855: 32-bit register bus over MDIO page 4, internal PHYs at MDIO 1..5, its registers translated into the MT7531 model (CPU port 5). MT7988 built-in switch: the MT7531 model memory mapped at 0x15020000, SoC GbE PHYs (MT7988 IDs, eFuse calibration) |
| PHYs | mtk_eth.c |
MT7531 GPHY ×5; RTL8221B-VB-CG (C45, temperature sensor); Motorcomm YT8821 (extended registers, UTP/SerDes spaces, 2.5G status); MaxLinear GPY211C (C45, firmware version, mailbox, temperature sensor); Airoha EN8811H (buckpbus window on MDIO page 4, MCU firmware download, 2.5G status, LED registers); MT7981 built-in GbE PHY (calibration handshake); a PHY on MT7531 port 5; optional hardware reset GPIOs |
| GPIO / EINT | mtk_pinctrl.c |
buttons (reset/WPS, reset-hold-ms for timed presses), LED log, pad levels to board devices |
| LEDs | filogic_leds.c, ws2812b.c, hc595.c |
front panel LEDs from the preset: GPIO, PHY LED registers (RTL8221B, GPY211, EN8811H, MediaTek GbE / 2.5G PHYs, also of switch ports), WS2812B on SPI, 74HC595 shift registers on SPI (latched on CS), PWM channels; blinking detection; led-state |
| USB | QEMU xHCI + MTK IPPC | USB 2.0 / 3.0 connector |
| Wi-Fi | mtk_wmac.c |
WFDMA rings + emulated WM/WA firmware command interface: firmware loads, both bands come up, hostapd runs, nothing is on the air (scans are empty) |
| PCIe (MT7987, MT7988) | mtk_pcie.c |
MediaTek Gen3 host: link, CFGNUM configuration TLPs, INTx, MSI capture, with QEMU's PCIe root port |
| Wi-Fi 7 card (MT7987) | mt7992_wifi.c |
MT7992 for mt7996e: BAR0 remap windows, WFDMA rings, patch/RAM download, UNI command results, TX free events; radio silent |
| Crypto (EIP-97) | ID only | the safexcel driver detects "no packet engine" and disables itself; software crypto is used |
| WED | mtk_wmac.c, mtk_wo.c, reset bits |
for MediaTek's vendor Wi-Fi driver (stock firmware: mt_wifi + mtk_warp): Wi-Fi RX enable and interrupts through WED, and the WO offload CPU as a message stub (CCIF mailbox and rings; every request answered, heartbeat, booted) so the driver does not wait 3 s per message. No offload datapath; OpenWrt's mt7915e uses WED only with wed_enable=1 (not tested against the WO stub) |
A new pcap netdev (net/pcap.c, libpcap / Npcap loaded at run time)
attaches a port to a host adapter like a bridged adapter — used by the
Windows launcher.
A NAND folder contains partition dumps without OOB. Every file whose name
contains mtdN becomes partition N; files are concatenated in order mtd0,
mtd1, … into the full flash, e.g. mt7981.mtd0.BL2.bin,
mt7981.mtd1.u-boot-env.bin, mt7981.mtd2.Factory.bin,
mt7981.mtd3.bdinfo.bin, mt7981.mtd4.FIP.bin, mt7981.mtd5.ubi.bin.
Everything the router writes (settings, sysupgrade, U-Boot env) is written
back into these files. Dumps from a real router (cat /dev/mtdN > name.mtdN.label.bin) can be used directly. With flash=nor the folder is
the SPI-NOR contents (e.g. BL2, u-boot-env, Factory, bdinfo, FIP,
firmware); with nor-nand=on the NAND files are in its
subfolder nand. Files smaller than the flash (a 128 MB dump started with
nand=256, say) are completed on the first start: the last file grows
with erased bytes (0xff) to the flash size, so what the router writes
there is kept.
tools/prepare-nand.sh[--stock DIR [--nor] | --local DIR] [--flash-mb N] [--no-bdinfo] PROFILE [VERSION] [OUTDIR]— builds a NAND folder for an OpenWrt device profile: downloads the official OpenWrt U-Boot images (PROFILE-ubootmod-*orPROFILE-*, sha256 verified), or uses own builds (--local), or keeps a vendor BL2/FIP (--stock) with the OpenWrtsysupgrade.binin the vendor layout;--no-bdinfofor boards without a bdinfo partition,--norfor SPI-NOR boards (vendor BL2/FIP + the OpenWrtsysupgrade.binin thefirmwarepartition),--soc mt7986for MT7986 boards (partition filesmt7986.mtdN.*, minimal Wi-Fi EEPROM in an empty Factory: chip ID, MAC, 4 paths, 20 dBm target TX power),--ubi-fipfor "spim-nand-ubi" BL2s (BL2 at 0, UBI from 0x200000 with the FIP as volumefip),--ubi-factory SIZE(Factory as UBI volumefactory, Asus BT8),--emmc [--emmc-layout L](eMMC image, see MT7988),--nor-nand rutc50|openwrt-one(SPI-NOR + SPI-NAND folder, see above). Factory/bdinfo come fromfactory/.tools/mknand.py— create / edit images:create(BL2, FIP, Factory, bdinfo, UBI from.itbor asysupgrade.bin),write --part fip,read,split,join,--flash-mb 256,--no-bdinfo,nor(SPI-NOR folder:--partslayout,--rawwhole-chip image,--mac-part,--envU-Boot environment; with--nand-parts,--nand-file,--nand-sysupgradealso thenandsubfolder).tools/kernel-dts.sh— the kernel's MediaTek DTS with the DTS parts of OpenWrt's patches (work/kdts), for boards whose DTS is in the kernel tree (BPi-R4);tools/dts-leds.pyreads them there.tools/mkemmc.py— eMMC image (flash=emmc): BL2 in boot0, GPT user area with u-boot-env, factory, fip, kernel, rootfs (GL-MT6000 layout,--layout wh3000-pro, or--layout bpi-r4with--fit/--recovery(FIT into production / recovery)), OpenWrtsquashfs-factory.binin kernel + rootfs or--sysupgrade(sysupgrade tar),--boot-env(OpenWrt U-Boot environment that boots thekernelpartition).
Linux: emulator.sh — -P PRESET (-P list), -o OPTS
(override machine options, ram=), -n NANDDIR, -w bridge|user|offline|none
(offline = user-mode WAN with restrict=on: DHCP works, nothing leaves the PC),
-l isolated|nic|user|none (user = this PC only, forwards from the
preset), -p "1 3" (LAN ports), -u DIR (USB stick from a
folder, FAT16), -L DIR (console logs), -g (GPIO and LED log), -R (power on
with reset held 10 s → TFTP recovery), -d (unimplemented register log),
-S SOCK (headless: console on a unix socket, for scripts and CI;
socat -,raw,echo=0,escape=0x1d UNIX-CONNECT:SOCK to attach). WAN_EXTRA /
LAN_EXTRA in the environment are appended to the user-mode netdevs, e.g.
WAN_EXTRA=",guestfwd=tcp:10.0.2.100:80-cmd:nc 127.0.0.1 8000" gives the
router a local test server even with -w offline. Headless example, no
root needed (the router's web UI at http://127.0.0.1:8080/):
./emulator.sh -P cudy-wr3000p-v1 -w offline -l user -S work/console.sock &
socat -,raw,echo=0,escape=0x1d UNIX-CONNECT:work/console.sock # console, Ctrl-] detaches
echo quit | socat - UNIX-CONNECT:work/monitor.sock # stopHost networking: tools/host-bridge.sh (br0
with the NIC for WAN, isolated br-wrlan for LAN — LAN on the real
network would expose the router's DHCP/RA there).
Windows: emulator.exe — board preset (with editor: New / Edit / Save /
Save as / Delete), NAND folder, WAN/LAN (NAT, "this PC only" port forwards
to LuCI/SSH, or bridge to an adapter via Npcap; when Npcap does not let a
non-administrator open the adapter, it offers to restart as administrator),
USB folder, log folder,
Reset/WPS buttons, "Power + Reset: 10 s" (TFTP recovery), power off on
poweroff, built-in terminal (VT100, PuTTY-like 80×24 default with cell
snapping, select = copy, right click = paste, Ctrl+Shift+R fits the router
tty to the window), interface language switched on the fly
(languages/*.ini: English, Русский; add a language by
copying en.ini; tools/gen-lang-en.py
regenerates en.ini from the sources, --check lists untranslated keys).
The emulated Wi-Fi radios are silent. For real Wi-Fi, pass a USB Wi-Fi
dongle of this PC through to the router (usb-host=VID:PID, several
; separated; Windows launcher: "USB devices" → Choose…); OpenWrt drives it with its own driver as one
more radio (radio2, path …/usb1/1-2/1-2:1.0), next to the silent
built-in ones, and runs hostapd on it like on real hardware.
- Linux:
./emulator.sh … -o usb-host=148f:5370(QEMU built with libusb:libusb-1.0-0-dev). QEMU opens/dev/bus/usb/…, so the user needs write access (emulator.sh prints a udev rule otherwise); the host's driver releases the dongle while the router uses it. Windows: install UsbDk (the dongle stays on its Windows driver and is taken over only while the router runs) or bind the WinUSB driver to it with Zadig. - In OpenWrt install the dongle's driver once (WAN needs Internet), e.g.
Ralink RT5370 / RT3070:
apk add kmod-rt2800-usb rt2800-usb-firmware; MediaTek MT7601U:kmod-mt7601u; MT7612U:kmod-mt76x2u; Atheros AR9271:kmod-ath9k-htc. - Network → Wireless: enable
radio2(the dongle's band: a 2.4 GHz dongle gives 2.4 GHz; a dual-band one works in one band at a time — choose 5 GHz there), set SSID and key. The built-inradio0/radio1can be disabled, they are not on the air anyway.
Checked with a Ralink RT5370 (148f:5370) on a Cudy WR3000P with OpenWrt 25.12.5: AP on channel 6, WPA2, bridged to br-lan; on Linux and on Windows 11 with UsbDk.
emulator.sh Linux launcher
presets/ board presets (*.ini)
build.sh QEMU build (Linux) → README.build.linux.md
build-windows.sh Windows package (cross) → README.build.windows.md
qemu-patches/ patches on top of QEMU v10.1.0
tools/ prepare-nand.sh, mknand.py, host-bridge.sh,
gen_dramc_table.py (DRAMC status defaults)
tools/uboot-rutc50/ U-Boot of the Teltonika RUTC50 preset (sources,
build.sh, the built FIP)
windows/ launcher, preset editor, terminal (C#),
README.txt for the package
languages/ launcher texts (en.ini, ru.ini)
tests/ quick.py (console-driven checks), powercut.py
factory/ Factory (Wi-Fi EEPROM) and bdinfo (MAC) dumps
wr3000u/, tr3000/, wr3000/ vendor BL2/FIP dumps (not in git)
m3000/ own OpenWrt builds (not in git)
- Reference sources used to model the hardware: OpenWrt's Linux tree (vanilla + OpenWrt patches), U-Boot, mtk-openwrt TF-A, mt76, coreboot's MediaTek DRAMC code (MT8192/MT8195, same DRAMC generation).
-d unimplogs every access to registers without explicit modelling — the fastest way to find what new firmware polls.tests/quick.pystarts QEMU with the console on a socket and waits for console patterns with hard time limits, e.g.tests/quick.py -P cudy-tr3000-v1 --qemu="-netdev user,id=wan" 'Starting kernel@60' 'eth0: Link is Up@120'.tests/powercut.pyquits/resets QEMU at random moments and checks that BL2 still loads FIP.- The Windows build uses clang (native TLS): MinGW GCC's emulated TLS made
guest execution ~30 % slower; it is also PGO-optimised (5–17 % on real
Windows), and the launcher keeps QEMU out of Windows 11 efficiency mode.
The released Linux packages are PGO-optimised too (GCC,
PGO_NAND=nand-wr3000p ./build.sh, ~8–10 % on system-call heavy guest work). Profile (perf): most time goes to TCG's translation-block lookup, the device models are negligible; -O3/LTO gave nothing. QEMU already runs each of the two guest CPUs in its own host thread (MTTCG); one guest thread cannot be spread over more host cores. - OpenWrt marks the overlay "ready" only at the end of boot; rebooting earlier makes fstools wipe it (same as on hardware).
- Wi-Fi radio is silent (no stations, empty scans); the MCU command interface answers generically. WED offload is not emulated.
- PHY interrupts are not generated (link changes after boot are seen only by polling drivers).
- No EIP-97 packet engine; PCIe only on MT7987 (the MT7992 card); PWM/I2C are stubs.
- Speed: close to the real router. The kernel of a WR3000P brings its WAN link up about 18 s after start on the real router (MT7981B, 1.3 GHz), about 24 s in the emulator on an i7-12700H (Linux VM) and about as fast as on the real router on a fast Windows PC (3.6, with PGO and PAN; TCG).
CI (.github/workflows/build.yml) builds the Linux and Windows packages on
every push and boots one preset per distinct hardware combination with each
of them, on Linux and on a real Windows runner: WR3000P (DDR4, RTL8221B on
GMAC1, switch), WR3000H (DDR3 preloader), WR3000S (WAN on a switch port),
TR3000 (no switch, one LAN on the built-in PHY), Netis NX31 (flash layout
without bdinfo) and the MT7986 boards Redmi AX6000 (DDR4, WAN on a switch
port), Netcore N60 (DDR3, RTL8221B), N60 Pro (GPY211 on GMAC1 and on
switch port 5), GL.iNet GL-MT6000 (eMMC), Huasifei WH3000 Pro eMMC (vendor
eMMC layout), the MT7987 Bananapi BPi-R4
Lite (NAND on SPI2, FIP in UBI, internal 2.5G PHY), the Teltonika RUTC50
(SPI-NOR boot + SPI-NAND, own U-Boot) and the OpenWrt One (SPI-NAND boot
next to a SPI-NOR, EN8811H). Flash folders are built for these tests only, never
packaged.
To release, change VERSION and push to main: when the tag
v<VERSION> does not exist yet, CI builds with PGO, tests, creates the tag
and the GitHub Release with ...-linux-x86_64.tar.gz, ...-linux-aarch64.tar.gz
and ...-win64.zip
(no flash folders). Locally build-windows.sh also makes ...-dumps.zip:
only the flash folders it built (Router-Emulator/nand-*, nor-*,
emmc-*), to unpack over a package.
GPL-2.0-or-later, like QEMU (the emulator is a set of QEMU patches plus tools around it): see LICENSE. Firmware images are not part of this repository; OpenWrt and vendor firmware keep their own licenses.