Davolink DVW-632 part #2: breaking into the Minion router

It’s been quite a while since the first post on this device, and a lot has happened in the meantime: Most notably, the first devices based on ipq5018 chipsets have been supported in OpenWrt by the start of this year.

Being curious about another device announced with this chipset, it was about time to dig Kevin back out and start learning about how device support works for this new subtarget in qualcommax.

The main hassle was interfacing the SPI NAND flash, which comes in a package called WSON8, that is way more challenging to desolder (probably also glued) or even attach wires to than an SOIC-8, for example. Also no clips seem to be available, as there are no pins, but just QFN style solder pads on the sides.

The pinout is the same as with the “25” style SPI NOR flashes though, so a CH341A-based programmer can be used with the tool SNANDer from https://github.com/Droid-MAX/SNANDer.
With all 8 lines (including 3.3V) connected, the AMS1117 in the programmer turned quite hot, trying to supply the whole board, but weirdly the flash chip was still detected at some point (after attaching and then removing the router’s power supply). I had no luck with the flash chip’s ECC though, so I went for disabling ECC: SNANDer -I -r dump.bin.
This took a while, but even with several attempts there were always flipped bits at varying locations in the output, maybe the wires were too long, or the (apparently hardcoded) SPI frequency should be decreased in the tool?

This would not have been a reliable way of flashing a full OpenWrt image after all, and even from the flash dump it was hard to find a definitive mtd partition layout (both binwalk and tools for reading UBI seemed to give up on the damaged flash dump, even after stitching a combined “best of three” image which had at least most of the bitflips from single transmission errors fixed).

Another attempt was trying to figure out what those lines on the 8-pin connector J2 next to the SPI flash are for. There is a device called “JTAGulator” which is quite expensive though, but it turned out people have ported the basic idea of brute-forcing JTAG pins to more readily available hardware platforms, i.e. RP 2040 based boards: https://github.com/Aodrulez/blueTag
But still no luck with this, maybe JTAG is just being disabled during the very early stages of uboot execution, or it needs more time to try all possible permutations, but there seemed to be no way to halt the CPU at that point, i.e. before pins would be muxed as GPIOs or for use with other peripherals.

So what else could we try for breaking into this thing, e.g. obtaining a uboot shell or something?
With wires attached to the NAND flash already, the idea of glitching seemed like a feasible thing to try next: When shorting flash chip data pins very early during boot, nothing is printed to the serial console, as the hardware boot ROM cannot even successfully load the `SBL1` partition from NAND.
When glitching later, we would see the serial output of SBL1, but get a bootloop when SBL1 could not successfully load uboot from the APPSBL partition.
Quickly after uboot was loaded, messing with flash data lines causes uboot to fail reading its environment from APPSBLENV (checksum fail), and this will result in interesting behaviour:

qpic_nand_read_page_scope_multi_page: ecc failure while reading from 480000
NAND read from offset 480000 failed -74
*** Warning - readenv() failed, using default environment

uboot would still boot the kernel, but fall back to its hardcoded default environment, which also affects the bootargs variable that contains the kernel command line. As a result, we finally get a “real” bootlog with kernel messages, including the mtd layout =)

[ 3.824696] nand: device found, Manufacturer ID: 0xc8, Chip ID: 0x41
[ 3.824723] nand: ESMT GD5F1GQ5REYIG SPI NAND 1G
[ 3.830848] nand: 128 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 128
[ 3.835718] qcom-nandc 79b0000.qpic-nand: x4 mode enabled already remotely
[ 3.845963] qcom-nandc 79b0000.qpic-nand: Error in reading max io macro clock from dts
[ 3.849886] 22 fixed-partitions partitions found on MTD device qcom_nand.0
[ 3.857733] Creating 22 MTD partitions on "qcom_nand.0":
[ 3.864536] 0x000000000000-0x000000080000 : "0:SBL1"
[ 3.871459] 0x000000080000-0x000000100000 : "0:MIBIB"
[ 3.876462] 0x000000100000-0x000000140000 : "0:BOOTCONFIG"
[ 3.881102] 0x000000140000-0x000000180000 : "0:BOOTCONFIG1"
[ 3.886559] 0x000000180000-0x000000280000 : "0:QSEE"
[ 3.892697] 0x000000280000-0x000000380000 : "0:QSEE_1"
[ 3.897932] 0x000000380000-0x0000003c0000 : "0:DEVCFG"
[ 3.902120] 0x0000003c0000-0x000000400000 : "0:DEVCFG_1"
[ 3.907269] 0x000000400000-0x000000440000 : "0:CDT"
[ 3.912677] 0x000000440000-0x000000480000 : "0:CDT_1"
[ 3.917385] 0x000000480000-0x000000500000 : "0:APPSBLENV"
[ 3.922863] 0x000000500000-0x000000640000 : "0:APPSBL"
[ 3.928908] 0x000000640000-0x000000780000 : "0:APPSBL_1"
[ 3.933866] 0x000000780000-0x000000880000 : "0:ART"
[ 3.939155] 0x000000880000-0x000000900000 : "0:TRAINING"
[ 3.943253] 0x000000900000-0x000003300000 : "rootfs"
[ 3.985568] mtd: device 15 (rootfs) set to be root filesystem
[ 3.985883] mtdsplit: no squashfs found in "rootfs"
[ 3.990325] 0x000003300000-0x000005d00000 : "rootfs_1"
[ 4.033480] 0x000005d00000-0x000006600000 : "0:DVCFG"
[ 4.042461] 0x000006600000-0x000006f00000 : "0:DVCFG_1"
[ 4.051558] 0x000006f00000-0x000007f00000 : "0:LOG"
[ 4.066738] 0x000007f00000-0x000007f80000 : "0:DVFLAG"
[ 4.068329] 0x000007f80000-0x000007fc0000 : "0:DVMON"
[ 4.072864] spi_qup 78b5000.spi: IN:block:16, fifo:64, OUT:block:16, fifo:64
[ 4.077044] spi-nor spi0.0: unrecognized JEDEC id bytes: 00 00 00 00 00 00

of course they did not even care about disabling the SPI NOR flash here…

Looking at the uboot environment at 0x480000, the bootcmd variable initially contains

bootargs=console=ttyMSM0,115200n8
bootcmd=bootipq; bootswap
bootdelay=1
bootdelay_=0

so technically we can just try to prepend tftpboot and bootm here, fix the crc32 header (checksum over the environment variables starting at 0x4, zero-terminated strings, file terminated by two 0x00 bytes which are included in the crc32, rest padded with 0xFF).

Writing stuff back to the flash was a bit more challenging though (with the flash chip spuriously supplied via the spi programmer; reading was easier as it requires much less power).
SNANDer -a 0x480000 -l 0x20000 -w appsblenv_patched.bin

Eventually it somehow worked, though I couldn’t say specifically how to reproduce it – it must have been a combination of padding the rest of the environment with 0xFF, erasing the flash area first in a separate command (using -e with SNANDer) and just a bit of trial and error. I definitely recommend desoldering the flash chip if anyone would like to attempt this!
Eventually, the contents read back identical, but still it would not boot: There was an error about an ECC failure when reading the uboot environment printed to the serial console, and it still booted OEM firmware using the default environment, even though a repeated readout with the SPI programmer revealed the environment was exactly the one I had flashed. So this must have been some internal error in the flash memory controller (which has been writing stuff under conditions way out of spec), but after a full boot and restart, this was fixed…

And then suddenly this happened:

PCI Link Intialized
PCI Link Intialized
No ethernet found.
Wrong Image Format for bootm command
ERROR: can't get kernel image!

Net:   cmbblk is stable 5
MAC0 addr:0:11:22:33:44:55
PHY ID1: 0x4d
PHY ID2: 0xd0c0
MAC1 addr:0:11:22:33:44:56
PHY ID1: 0x4d
PHY ID2: 0xd101
board_update_caldata: Unable to find slot-Id, Default CapIn/CapOut values used
, eth0, eth1
IPQ5018#

“No ethernet found” means that my tftpboot command injected to the environment was actually being called, and there was no link on any of the two network ports at that time, so it aborted, resulting in bootm trying to boot from unitialized RAM (“Wrong Image Format for bootm command”), and eventually we get what the initial glitching attemps failed to provide: a uboot shell!

IPQ5018# help
?       - alias for 'help'
ar8xxx_dump- Dump ar8xxx registers
base    - print or set address offset
bdinfo  - print Board Info structure
bootelf - Boot from an ELF image in memory
bootipq - bootipq from flash device
bootm   - boot application image from memory
bootp   - boot image via network using BOOTP/TFTP protocol
bootswap- bootswap from flash device
bootvx  - Boot vxWorks from an ELF image
bootz   - boot Linux zImage image from memory
canary  - test stack canary
chpart  - change active partition
cmp     - memory compare
coninfo - print console devices and information
cp      - memory copy
crc32   - checksum calculation
dhcp    - boot image via network using DHCP/TFTP protocol
dm      - Driver model low level access
echo    - echo args to console
editenv - edit environment variable
env     - environment handling commands
erase   - erase FLASH memory
exectzt - execute TZT

exit    - exit script
false   - do nothing, unsuccessfully
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls   - list files in a directory (default /)
fatsize - determine a file's size
fatwrite- write file into a dos filesystem
fdt     - flattened device tree utility commands
flash   - flash part_name
        flash part_name load_addr file_size

flasherase- flerase part_name

flinfo  - print FLASH memory information
fuseipq - fuse QFPROM registers from memory

go      - start application at address 'addr'
gpio    - query and control gpio pins
help    - print command description/usage
i2c     - I2C sub-system
imxtract- extract a part of a multi-image
ipq5018_mdio- IPQ5018 mdio utility commands
ipq_mdio- IPQ mdio utility commands
is_sec_boot_enabled- check secure boot fuse is enabled or not

itest   - return true/false on integer compare
loop    - infinite loop on address range
md      - memory display
mii     - MII utility commands
mm      - memory modify (auto-incrementing address)
mmc     - MMC sub system
mmcinfo - display MMC info
mtdparts- define flash/nand partitions
mtest   - simple RAM read/write test
mw      - memory write (fill)
nand    - NAND sub-system
nboot   - boot from NAND device
nfs     - boot image via network using NFS protocol
nm      - memory modify (constant address)
part    - disk partition related commands
pci     - list and access PCI Configuration Space
ping    - send ICMP ECHO_REQUEST to network host
printenv- print environment variables
protect - enable or disable FLASH write protection
qpic_nand- Switch between SBL and Linux kernel page on 4K NAND Flash.
reset   - Perform RESET of the CPU
run     - run commands in an environment variable
runmulticore- Enable and schedule secondary cores
saveenv - save environment variables to persistent storage
secure_authenticate- authenticate the signed image

setenv  - set environment variables
setexpr - set environment variable as the result of eval expression
sf      - SPI flash sub-system
showvar - print local hushshell variables
sleep   - delay execution for some time
smeminfo- print SMEM FLASH information
source  - run script from memory
test    - minimal test like /bin/sh
tftpboot- boot image via network using TFTP protocol
tftpput - TFTP put command, for uploading files to a server
true    - do nothing, successfully
tzt     - load and run tzt

uart    - UART sub-system
ubi     - ubi commands
usb     - USB sub-system
usbboot - boot from USB device
version - print monitor, compiler and linker version
zip     - zip a memory region
IPQ5018# printenv
baudrate=115200
bootargs=root=/dev/nfs nfsroot=: ip=::::::off
bootcmd=bootp; setenv bootargs root=/dev/nfs nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off; bootm
bootdelay=5
eth2addr=00:11:22:33:44:55
eth3addr=00:11:22:33:44:56
ethact=eth0
fdt_high=0x4A400000
fdtcontroladdr=4a9d4004
flash_type=11
machid=8040004
serial_num=DVW632IX0000012345
soc_hw_version=20180101
soc_version_major=1
soc_version_minor=1
stderr=serial@78AF000
stdin=serial@78AF000
stdout=serial@78AF000

Environment size: 559/262140 bytes
IPQ5018#

Great, so it’s finally time to build an initramfs and feed it to uboot via ttpboot and see if it boots…

Since expectations were high at this point, the eventual frustration was even bigger: I currently can no longer get this thing to boot past the SBL1 output at all, it seemes quite bricked, maybe actual hardware damage this time. I might try reflashing SBL1 or APPSBL again the same way, but it was cumbersome already the last time, and how could I be safe my backup is actually a perfect image without bitflips during transmission (ubootenv has a crc32 which I had to reconstruct anyways)?

One thing I noticed – way too late, obviously: The flash chip runs at 1.8V, not 3.3V.
Guess I have used up most of the magic smoke by now. But it might be hardware damage in some other part of the router.

So I guess this project is no longer about OpenWrt now, but generic hardware debugging and repair, probably starting with a thermal camera… so stay tuned, at least the intention of learning something about the ipq50xx target was partly fulfilled =)

Using the D-Link DWA-X1850 Wi-Fi 6 USB adapter on Linux (RTL8832AU 802.11ax)

After the introduction of wireless routers, phones and PCIe cards featuring the next generation of Wi-Fi, known as 802.11ax (or simply Wi-Fi 6), finally the first USB 3.0 adapters have arrived on the market:

The ASUS USB-AX56 adapter has two external antennas, while the D-Link DWA-X1850 comes in a more compact case with built-in antennas.
Both support 2 streams dual band on 2.4GHz and 5GHz, labelled “AX1800”, which translates as:

  • 40 MHz channel width on 2.4GHz, using MCS index 11 and short GI (0.8µs), resulting in a nominal rate of 573.5 Mbps
  • 80 MHz channel width on 5 GHz, also MCS 11 with short GI (0.8µs), resulting in the nominal rate of 1201 Mbps

Combining both rates, these are marketed as AX1800, although you typically can’t bond connections and will rather end up with a Link Rate of 1201 Mbit/s only, which may be somewhere around 500 Mbit/s of real throughput (all while assuming you are located very close to the router, and none of your neighbours are transmitting on the same wifi channel, of course).

While the Link Rate will degrade quickly when a few walls need to be penetrated, the receive sensitivity of this new generation of chips can still be considered superior to previous ac adapters, although the limitation to 2 streams might be an issue. Especially if your router supports AC with 3×3 or 4×4, older adapters like the infamous “Death Star” D-Link DWA-192 might still be a better choice.
It uses RTL8814AU and supports AC1900, i.e.

  • 3 spatial streams with 40 MHz channel width on 2.4GHz, using MCS index 9 and short GI (0.4µs), resulting in 600Mbps
  • 3 spatial streams with 80 MHz channel width on 5GHz, using MCS index 9 and short GI (0.4µs), resulting in 1300Mbps

This may seem like not much of a difference, but keep in mind that MCS11 will degrade more quickly with a few walls in between, and when the connection is at MCS9, 2 streams of AX will only be a rate of 960.Mbps compared to 1300 with 3 streams AC.

But back to the first USB AX adapters hitting the markets right now:
Both the ASUS USB-AX56 and D-Link DWA-X1850 are based on the Realtek RTL8832AU chipset, which is the 2-stream variant of the RTL8852AU – this is the most important information when it comes to finding a driver that works on Linux.

When searching github, you will find the repository
https://github.com/shiqishao/RTL8852AU_WiFi_linux_v1.15.0.1-0-g487ee886.20210714
which contains the driver source code from Realtek. There are also a few documents that explain how to introduce your device’s USB VID and PID into the driver:
The relevant file to modify here is os_dep/linux/usb_intf.c, the USB VID e.g. for D-Link is 0x2001, the PID for the DWA-X1850 adapter is 0x3321. For the ASUS USB-AX56, it would be 0B05:1997.

You can find this already patched in my fork of the repo on github:
https://github.com/s-2/RTL8852AU_WiFi_linux_v1.15.0.1-0-g487ee886.20210714

// Update: The most current maintained fork of this driver is now available from the repo of lwfinger, please use this instead: https://github.com/lwfinger/rtl8852au

Now just clone that repo, make and sudo make install, then re-plug the dongle.
After being plugged in, the DWA-X1850 is in USB Mass Storage mode, which can be switched to network adapter mode by ejecting the virtual drive (eventually, it seems this device should be added to the USB modeswitch project).

The blue status LED is currently not working, maybe this needs some addition GPIO definition which is specific to this device.

Also keep in mind that this driver, although being built from source, uses the Realtek proprietary structure, which may not be what the Linux Wireless developers are aiming for.

A corresponding Linux Wireless compatible driver would be “rtw89”, which currently supports only the PCIe version of RTL8852, but I’m sure this may soon be updated to include the USB variants, and one day be available with Ubuntu and other common distributions. For now, at least we have the Realtek driver.