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 =)

Davolink DVW-632 aka “Kevin”: Teardown of the Minions router (part 1)

Every once in a while, a piece of hardware goes viral for standing out from the competition in a certain aspect. For consumer-grade wireless routers, it seems case designers have mostly been looking at Sci-Fi movies for inspiration over the past decades, until a company named Davolink chose to license a pair of well-known characters from a much different genre of movies:

Two models are available: The smaller model DVW-642 (“Bob”) is an AX1800 dual-band wireless router, while the much larger DVW-632 (“Kevin”) is an AX5400 tri-band device, which even supports the quite new 6 Ghz band known as Wi-Fi 6E.

There is little information available on the hardware internals on these yet, the FCC filings available for Kevin do not even contain internal photos (yet?), there is only the label sample and a test setup showing a view on the PCB from the distance:
https://fccid.io/RZEDVW-632/

However, a Reddit user simply asked Davolink support for information on the chipsets:

https://www.reddit.com/r/HomeNetworking/comments/19bku93/comment/kitdu0x/

And it turns out Bob is based on a Realtek chipset, which currently does not have a high chance of getting OpenWrt support anytime soon.

However, Kevin is supposed to be based on the quite new Qualcomm AX OpenWrt target, specifically using the IPQ5018 chipset – which is not exactly supported yet, however that one looks way more like a work-in-progress at the moment, considering there are similar chipsets in the target, which is already part of OpenWrt, and there is also ongoing discussion on devices using IPQ5018, e.g. from Linksys:
https://forum.openwrt.org/t/ipq5018-potential-future-support-for-linksys-mx2000-atlas-6-mx5500-atlas-6-pro/164055/

The case for Kevin opens very easily, only a few screws need to be removed to access the inner assembly, consisting of the PCB, massive heatsinks and a white plastic frame. After all, this design seems quite modular, suggesting the same hardware could be re-used for various other styles of profitable contemporary merchandise.

There is a TTL UART header to the left, with Pin 4 being GND and both data lines in the center:

Unfortunately, there’s not really much to see during boot, and I found no way to interrupt the bootloader yet:


Format: Log Type - Time(microsec) - Message - Optional Info
Log Type: B - Since Boot(Power On Reset),  D - Delta,  S - Statistic
S - QC_IMAGE_VERSION_STRING=BOOT.BF.3.3.1.1-00075
S - IMAGE_VARIANT_STRING=MAACANAZA
S - OEM_IMAGE_VERSION_STRING=CRM
S - Boot Config, 0x000002c5
B -       127 - PBL, Start
B -      1560 - bootable_media_detect_entry, Start
B -      3647 - bootable_media_detect_success, Start
B -      3651 - elf_loader_entry, Start
B -      8822 - auth_hash_seg_entry, Start
B -      9183 - auth_hash_seg_exit, Start
B -    103041 - elf_segs_hash_verify_entry, Start
B -    172705 - PBL, End
B -    142069 - SBL1, Start
B -    203435 - GCC [RstStat:0x0, RstDbg:0x600000] WDog Stat : 0x4
B -    211548 - clock_init, Start
D -      7564 - clock_init, Delta
B -    219295 - boot_flash_init, Start
D -     15006 - boot_flash_init, Delta
B -    234362 - boot_config_data_table_init, Start
D -      4697 - boot_config_data_table_init, Delta - (575 Bytes)
B -    242139 - Boot Setting :  0x00030618
B -    248483 - CDT version:2,Platform ID:8,Major ID:4,Minor ID:0,Subtype:4
B -    255224 - sbl1_ddr_set_params, Start
B -    256657 - Pre_DDR_clock_init, Start
B -    262513 - Pre_DDR_clock_init, End
B -    904721 - do ddr sanity test, Start
D -        61 - do ddr sanity test, Delta
B -    909388 - Image Load, Start
D -    244854 - QSEE Image Loaded, Delta - (580996 Bytes)
B -   1155096 - Image Load, Start
D -     13908 - DEVCFG Image Loaded, Delta - (13592 Bytes)
B -   1169065 - Image Load, Start
D -    182085 - APPSBL Image Loaded, Delta - (433660 Bytes)
B -   1351241 - QSEE Execution, Start
D -        30 - QSEE Execution, Delta
B -   1357707 - SBL1, End
D -   1218292 - SBL1, Delta
S - Flash Throughput, 2441 KB/s  (1029495 Bytes,  421684 us)
S - DDR Frequency, 800 MHz
S - Core 0 Frequency, 800 MHz


U-Boot 2016.01-svn6050 (Nov 28 2022 - 10:45:07 +0000)

cmbblk is stable 5
ART partition read failed..
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
QPIC controller support serial NAND
Serial Nand Device Found With ID : 0xc8 0x41
Serial NAND device Manufacturer:GD5F1GQ5REYIG
Device Size:256 MiB, Page size:2048, Spare Size:128, ECC:8-bit
DAVO_TODO : [qpic_spi_nand_config:1462] dev->id=41c841c8 dev->vendor=c8
sdhci: Node Not found, skipping initialization
PCI Link Intialized
PCI Link Intialized

Starting kernel ...

131072+0 records in
131072+0 records out
131072 bytes (128.0KB) copied, 0.822810 seconds, 155.6KB/s
131072+0 records in
131072+0 records out
131072 bytes (128.0KB) copied, 0.824732 seconds, 155.2KB/s
131072+0 records in
131072+0 records out
131072 bytes (128.0KB) copied, 0.775230 seconds, 165.1KB/s
Loading cnss2:  bdf_integrated=0x24 bdf_pci0=0x60 bdf_pci1=0xb0
mount: can't find /lib/firmware/IPQ5018/BT_FW in /etc/fstab
BT FW mount is failed
 WIFI FW mount is successful
acfg_tool: Issuing blocking call to wait for events

========= FW INFO =========
2x2
jenkins
2023-09-15 12:39:25
DVW-632X
r7331
83e9805dded1d4e3e22fc60c1f146281
1.00.04
Primary Boot 0
===========================
----------------------------------- start_event_vap() - start(wifi0)
/tmp/config/fastwifi_cfg.tgz: OK
tmp/fastwifi_wifi0
tmp/fastwifi_wifi1
tmp/fastwifi_wifi2
var/run/hostapd-ath0.conf
var/run/hostapd-ath01.conf
var/run/hostapd-ath1.conf
var/run/hostapd-ath11.conf
var/run/hostapd-ath2.conf
var/run/hostapd-ath21.conf
Start wi-fi configuration wifi0
----------------------------------- start_event_vap() - start(wifi1)
Start wi-fi configuration wifi1
----------------------------------- start_event_vap() - start(wifi2)
Start wi-fi configuration wifi2
start config vap vap_bh0
error_handler received : -22
Failed to send message to driver Error:-22
start config vap vap_bh1
Following channels are blocked from Channel selection algorithm
 -band 2[52] [56] [60] [64] [100] [104] [108] [112] [116] [120] [124] [128] [132] [136] [140] [144] [149] [153] [157] [161] [165]
skip wifi reload. fast boot in progress
error_handler received : -22
Failed to send message to driver Error:-22
start config vap vap_bh2
Following channels are blocked from Channel selection algorithm
 -band 3[1] [5] [9] [13] [17] [21] [25] [29] [33] [41] [45] [49] [57] [61] [65] [73] [77] [81] [89] [93] [97] [105] [109] [113] [121] [125] [129] [137] [141] [145] [153] [157] [161] [169] [173] [177] [185] [189] [193] [201] [205] [209] [213] [217] [221] [225] [229] [233]
error_handler received : -22
Failed to send message to driver Error:-22
OK
start config vap vap00
OK
start config vap vap10
OK
start config vap vap20
OK
----------------------------------- start_event_vap() - end(wifi0) -  18 seconds
OK
----------------------------------- start_event_vap() - end(wifi1) -  18 seconds
OK
 WIFI FW mount is successful
OK
**** Platform Name: ap-mp03.5-c1 *****
Run REPACD
Copy ART caldata from /dev/mtdblock13 to /tmp/virtual_art.bin
----------------------------------- start_event_vap() - end(wifi2) -  21 seconds
#### easy mesh setup : controller ####
----------------------------------- ezmesh stop_service() - start
----------------------------------- ezmesh stop_service() - end - 0 seconds
----------------------------------- ezmesh start_service() - start
ezmesh: starting daemon
----------------------------------- ezmesh start_service() - end - 1 seconds
----------------------------------- wsplcd stop_service() - start
----------------------------------- wsplcd stop_service() - end - 0 seconds
----------------------------------- wsplcd start_service() - start
wsplcd: starting daemon
----------------------------------- wsplcd start_service() - end - 8 seconds
### Integrity check is passed for SHA-256 ###

We also see a GigaDevice GD5F1GQ5REY1G SPI NAND flash, with pins easily accessible without further disassembly. Here’s the datasheet:

https://www.gigadevice.com.cn/Public/Uploads/uploadfile/files/20230322/DS-00888-GD5F1GQ5UExxG-Rev1.5.pdf

So the next step from here would most probably be dumping the contents, although there is a good chance everything might be encrypted in there…