{"id":276,"date":"2025-08-13T22:00:25","date_gmt":"2025-08-13T21:00:25","guid":{"rendered":"http:\/\/sebastianschaper.net\/?p=276"},"modified":"2025-08-13T22:46:57","modified_gmt":"2025-08-13T21:46:57","slug":"davolink-dvw-632-part-2-breaking-into-the-minion-router","status":"publish","type":"post","link":"http:\/\/sebastianschaper.net\/index.php\/archives\/276","title":{"rendered":"Davolink DVW-632 part #2: breaking into the Minion router"},"content":{"rendered":"\n<p>It&#8217;s been quite a while since the <a href=\"http:\/\/sebastianschaper.net\/index.php\/archives\/265\">first post on this device<\/a>, 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.<br><br>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 <code>qualcommax<\/code>.<br><br>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.<br><br>The pinout is the same as with the &#8220;25&#8221; style SPI NOR flashes though, so a CH341A-based programmer can be used with the tool SNANDer from <a href=\"https:\/\/github.com\/Droid-MAX\/SNANDer\">https:\/\/github.com\/Droid-MAX\/SNANDer<\/a>.<br>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&#8217;s power supply). I had no luck with the flash chip&#8217;s ECC though, so I went for disabling ECC: <code>SNANDer -I -r dump.bin<\/code>.<br>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?<br><br>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 &#8220;best of three&#8221; image which had at least most of the bitflips from single transmission errors fixed).<br><br>Another attempt was trying to figure out what those lines on the 8-pin connector <code>J2<\/code> next to the SPI flash are for. There is a device called &#8220;JTAGulator&#8221; 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: <a href=\"https:\/\/github.com\/Aodrulez\/blueTag\">https:\/\/github.com\/Aodrulez\/blueTag<\/a><br>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.<br><br>So what else could we try for breaking into this thing, e.g. obtaining a uboot shell or something?<br>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 `<code>SBL1`<\/code> partition from NAND.<br>When glitching later, we would see the serial output of SBL1, but get a bootloop when SBL1 could not successfully load uboot from the <code>APPSBL<\/code> partition.<br>Quickly after uboot was loaded, messing with flash data lines causes uboot to fail reading its environment from <code>APPSBLENV<\/code> (checksum fail), and this will result in interesting behaviour:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>qpic_nand_read_page_scope_multi_page: ecc failure while reading from 480000\nNAND read from offset 480000 failed -74\n*** Warning - readenv() failed, using default environment<\/code><\/pre>\n\n\n\n<p>uboot would still boot the kernel, but fall back to its hardcoded default environment, which also affects the <code>bootargs<\/code> variable that contains the kernel command line. As a result, we finally get a &#8220;real&#8221; bootlog with kernel messages, including the mtd layout =)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91; 3.824696] nand: device found, Manufacturer ID: 0xc8, Chip ID: 0x41\n&#91; 3.824723] nand: ESMT GD5F1GQ5REYIG SPI NAND 1G\n&#91; 3.830848] nand: 128 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 128\n&#91; 3.835718] qcom-nandc 79b0000.qpic-nand: x4 mode enabled already remotely\n&#91; 3.845963] qcom-nandc 79b0000.qpic-nand: Error in reading max io macro clock from dts\n&#91; 3.849886] 22 fixed-partitions partitions found on MTD device qcom_nand.0\n&#91; 3.857733] Creating 22 MTD partitions on \"qcom_nand.0\":\n&#91; 3.864536] 0x000000000000-0x000000080000 : \"0:SBL1\"\n&#91; 3.871459] 0x000000080000-0x000000100000 : \"0:MIBIB\"\n&#91; 3.876462] 0x000000100000-0x000000140000 : \"0:BOOTCONFIG\"\n&#91; 3.881102] 0x000000140000-0x000000180000 : \"0:BOOTCONFIG1\"\n&#91; 3.886559] 0x000000180000-0x000000280000 : \"0:QSEE\"\n&#91; 3.892697] 0x000000280000-0x000000380000 : \"0:QSEE_1\"\n&#91; 3.897932] 0x000000380000-0x0000003c0000 : \"0:DEVCFG\"\n&#91; 3.902120] 0x0000003c0000-0x000000400000 : \"0:DEVCFG_1\"\n&#91; 3.907269] 0x000000400000-0x000000440000 : \"0:CDT\"\n&#91; 3.912677] 0x000000440000-0x000000480000 : \"0:CDT_1\"\n&#91; 3.917385] 0x000000480000-0x000000500000 : \"0:APPSBLENV\"\n&#91; 3.922863] 0x000000500000-0x000000640000 : \"0:APPSBL\"\n&#91; 3.928908] 0x000000640000-0x000000780000 : \"0:APPSBL_1\"\n&#91; 3.933866] 0x000000780000-0x000000880000 : \"0:ART\"\n&#91; 3.939155] 0x000000880000-0x000000900000 : \"0:TRAINING\"\n&#91; 3.943253] 0x000000900000-0x000003300000 : \"rootfs\"\n&#91; 3.985568] mtd: device 15 (rootfs) set to be root filesystem\n&#91; 3.985883] mtdsplit: no squashfs found in \"rootfs\"\n&#91; 3.990325] 0x000003300000-0x000005d00000 : \"rootfs_1\"\n&#91; 4.033480] 0x000005d00000-0x000006600000 : \"0:DVCFG\"\n&#91; 4.042461] 0x000006600000-0x000006f00000 : \"0:DVCFG_1\"\n&#91; 4.051558] 0x000006f00000-0x000007f00000 : \"0:LOG\"\n&#91; 4.066738] 0x000007f00000-0x000007f80000 : \"0:DVFLAG\"\n&#91; 4.068329] 0x000007f80000-0x000007fc0000 : \"0:DVMON\"\n&#91; 4.072864] spi_qup 78b5000.spi: IN:block:16, fifo:64, OUT:block:16, fifo:64\n&#91; 4.077044] spi-nor spi0.0: unrecognized JEDEC id bytes: 00 00 00 00 00 00<\/code><\/pre>\n\n\n\n<p>of course they did not even care about disabling the SPI NOR flash here&#8230;<br><br>Looking at the uboot environment at <code>0x480000<\/code>, the <code>bootcmd<\/code> variable initially contains<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>bootargs=console=ttyMSM0,115200n8\nbootcmd=bootipq; bootswap\nbootdelay=1\nbootdelay_=0<\/code><\/pre>\n\n\n\n<p>so technically we can just try to prepend <code>tftpboot<\/code> and <code>bootm<\/code> 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).<br><br>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).<br><code>SNANDer -a 0x480000 -l 0x20000 -w appsblenv_patched.bin<\/code><br><br>Eventually it somehow worked, though I couldn&#8217;t say specifically how to reproduce it &#8211; 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 <code>-e<\/code> with SNANDer) and just a bit of trial and error. I definitely recommend desoldering the flash chip if anyone would like to attempt this!<br>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&#8230;<br><br>And then suddenly this happened:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PCI Link Intialized\nPCI Link Intialized\nNo ethernet found.\nWrong Image Format for bootm command\nERROR: can't get kernel image!\n\nNet:   cmbblk is stable 5\nMAC0 addr:0:11:22:33:44:55\nPHY ID1: 0x4d\nPHY ID2: 0xd0c0\nMAC1 addr:0:11:22:33:44:56\nPHY ID1: 0x4d\nPHY ID2: 0xd101\nboard_update_caldata: Unable to find slot-Id, Default CapIn\/CapOut values used\n, eth0, eth1\nIPQ5018#<\/code><\/pre>\n\n\n\n<p>&#8220;No ethernet found&#8221; means that my <code>tftpboot<\/code> 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 <code>bootm<\/code> trying to boot from unitialized RAM (&#8220;Wrong Image Format for bootm command&#8221;), and eventually we get what the initial glitching attemps failed to provide: a uboot shell!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>IPQ5018# help\n?       - alias for 'help'\nar8xxx_dump- Dump ar8xxx registers\nbase    - print or set address offset\nbdinfo  - print Board Info structure\nbootelf - Boot from an ELF image in memory\nbootipq - bootipq from flash device\nbootm   - boot application image from memory\nbootp   - boot image via network using BOOTP\/TFTP protocol\nbootswap- bootswap from flash device\nbootvx  - Boot vxWorks from an ELF image\nbootz   - boot Linux zImage image from memory\ncanary  - test stack canary\nchpart  - change active partition\ncmp     - memory compare\nconinfo - print console devices and information\ncp      - memory copy\ncrc32   - checksum calculation\ndhcp    - boot image via network using DHCP\/TFTP protocol\ndm      - Driver model low level access\necho    - echo args to console\neditenv - edit environment variable\nenv     - environment handling commands\nerase   - erase FLASH memory\nexectzt - execute TZT\n\nexit    - exit script\nfalse   - do nothing, unsuccessfully\nfatinfo - print information about filesystem\nfatload - load binary file from a dos filesystem\nfatls   - list files in a directory (default \/)\nfatsize - determine a file's size\nfatwrite- write file into a dos filesystem\nfdt     - flattened device tree utility commands\nflash   - flash part_name\n        flash part_name load_addr file_size\n\nflasherase- flerase part_name\n\nflinfo  - print FLASH memory information\nfuseipq - fuse QFPROM registers from memory\n\ngo      - start application at address 'addr'\ngpio    - query and control gpio pins\nhelp    - print command description\/usage\ni2c     - I2C sub-system\nimxtract- extract a part of a multi-image\nipq5018_mdio- IPQ5018 mdio utility commands\nipq_mdio- IPQ mdio utility commands\nis_sec_boot_enabled- check secure boot fuse is enabled or not\n\nitest   - return true\/false on integer compare\nloop    - infinite loop on address range\nmd      - memory display\nmii     - MII utility commands\nmm      - memory modify (auto-incrementing address)\nmmc     - MMC sub system\nmmcinfo - display MMC info\nmtdparts- define flash\/nand partitions\nmtest   - simple RAM read\/write test\nmw      - memory write (fill)\nnand    - NAND sub-system\nnboot   - boot from NAND device\nnfs     - boot image via network using NFS protocol\nnm      - memory modify (constant address)\npart    - disk partition related commands\npci     - list and access PCI Configuration Space\nping    - send ICMP ECHO_REQUEST to network host\nprintenv- print environment variables\nprotect - enable or disable FLASH write protection\nqpic_nand- Switch between SBL and Linux kernel page on 4K NAND Flash.\nreset   - Perform RESET of the CPU\nrun     - run commands in an environment variable\nrunmulticore- Enable and schedule secondary cores\nsaveenv - save environment variables to persistent storage\nsecure_authenticate- authenticate the signed image\n\nsetenv  - set environment variables\nsetexpr - set environment variable as the result of eval expression\nsf      - SPI flash sub-system\nshowvar - print local hushshell variables\nsleep   - delay execution for some time\nsmeminfo- print SMEM FLASH information\nsource  - run script from memory\ntest    - minimal test like \/bin\/sh\ntftpboot- boot image via network using TFTP protocol\ntftpput - TFTP put command, for uploading files to a server\ntrue    - do nothing, successfully\ntzt     - load and run tzt\n\nuart    - UART sub-system\nubi     - ubi commands\nusb     - USB sub-system\nusbboot - boot from USB device\nversion - print monitor, compiler and linker version\nzip     - zip a memory region\nIPQ5018# printenv\nbaudrate=115200\nbootargs=root=\/dev\/nfs nfsroot=: ip=::::::off\nbootcmd=bootp; setenv bootargs root=\/dev\/nfs nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off; bootm\nbootdelay=5\neth2addr=00:11:22:33:44:55\neth3addr=00:11:22:33:44:56\nethact=eth0\nfdt_high=0x4A400000\nfdtcontroladdr=4a9d4004\nflash_type=11\nmachid=8040004\nserial_num=DVW632IX0000012345\nsoc_hw_version=20180101\nsoc_version_major=1\nsoc_version_minor=1\nstderr=serial@78AF000\nstdin=serial@78AF000\nstdout=serial@78AF000\n\nEnvironment size: 559\/262140 bytes\nIPQ5018#<\/code><\/pre>\n\n\n\n<p>Great, so it&#8217;s finally time to build an initramfs and feed it to uboot via ttpboot and see if it boots&#8230;<br><br>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)?<br><br>One thing I noticed &#8211; way too late, obviously: The flash chip runs at 1.8V, not 3.3V.<br>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.<br><br>So I guess this project is no longer about OpenWrt now, but generic hardware debugging and repair, probably starting with a thermal camera&#8230; so stay tuned, at least the intention of learning something about the ipq50xx target was partly fulfilled =) <br><br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;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 &hellip; <a href=\"http:\/\/sebastianschaper.net\/index.php\/archives\/276\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Davolink DVW-632 part #2: breaking into the Minion router<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,48,5],"tags":[25,32,47,46],"class_list":["post-276","post","type-post","status-publish","format-standard","hentry","category-hardware","category-openwrt","category-software","tag-802-11ax","tag-linux","tag-minions","tag-openwrt"],"_links":{"self":[{"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/posts\/276","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/comments?post=276"}],"version-history":[{"count":3,"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/posts\/276\/revisions"}],"predecessor-version":[{"id":282,"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/posts\/276\/revisions\/282"}],"wp:attachment":[{"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/media?parent=276"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/categories?post=276"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/sebastianschaper.net\/index.php\/wp-json\/wp\/v2\/tags?post=276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}