Migrating Qualcomm Dragonwing / Ventuno Q to NVMe: Safe Kernel Handoff Explained
The Qualcomm Dragonwing (Arduino Ventuno Q) development platform is a capable ARM64 single-board computer, but its default 64 GB eMMC (typically ~55 GB usable) quickly becomes an I/O bottleneck under real-world workloads like compilation, Docker container orchestration, and on-device model inference.
While adding an M.2 NVMe SSD offers a significant leap in throughput and capacity, naively cloning the drive as you would on x86 will fail or render the board unbootable.
This post explains the mechanics of Qualcomm’s boot chain, why traditional cloning breaks, and how to execute a zero-risk migration using a Safe Kernel Handoff Architecture.
1. The Challenge with Qualcomm ARM64 Storage
On x86 platforms, cloning an OS to an NVMe drive is trivial: clone the disk, update the EFI boot order in BIOS, and reboot.
Qualcomm platforms differ fundamentally:
- Complex Partition Tables: The eMMC layout contains over 70 distinct partitions holding low-level vendor firmware (
tz,hyp,rpm,xbl), TrustZone components, modem binaries, and Device Tree Blobs (DTBs). - Firmware Coupling: The early-stage bootloader and UEFI firmware are hardcoded to initialize and query the eMMC controller.
- Factory Calibration Data (
/var/persist): Partition 70 (mmcblk0p70) contains persistent factory calibration parameters, including Wi-Fi/Bluetooth MAC addresses and power tables. Overwriting or disassociating this partition breaks wireless connectivity.
Attempting to wipe the eMMC or duplicate the entire 70-partition structure to NVMe will prevent the board from completing early-stage boot.
2. The Solution: Safe Kernel Handoff Architecture
Instead of replacing the eMMC entirely, we separate the boot loader role from the root filesystem role:
+-----------------------------------------------------------------+
| Qualcomm eMMC (Boot) |
| |
| [Power On] -> [UEFI Firmware] -> [GRUB] -> [Linux Kernel] |
| | |
+-----------------------------------------------------|-----------+
| Hands off
v Root (/)
+-----------------------------------------------------------------+
| M.2 NVMe SSD (Root) |
| |
| [initramfs] -> [Pivots root to NVMe UUID] -> [Userspace /] |
| |
+-----------------------------------------------------------------+
- eMMC stays the boot orchestrator: The platform initializes hardware, executes UEFI, loads GRUB, and pulls the kernel image (
vmlinuz) andinitrddirectly from eMMC. - Initramfs shifts the root to NVMe: When the kernel boots, the initramfs loads the NVMe driver, detects the M.2 SSD, mounts it by UUID as root (
/), and launches userspace (systemd) from high-speed storage. - Critical eMMC partitions stay connected:
/boot/efiand/var/persistremain mounted from their original eMMC partitions via filesystem labels. - Instant Rollback: Because the original root partition (
/dev/mmcblk0p71) remains intact, reverting to eMMC requires only a single line edit in GRUB.
3. Step-by-Step Migration Breakdown
Step 1: Pre-Flight Environment Validation
Before modifying partitions, verify disk boundaries and runtime safety constraints:
CURRENT_ROOT=$(findmnt -n -o SOURCE /)
if [[ "$CURRENT_ROOT" == *"nvme"* ]]; then
echo "[-] ERROR: System is already running from an NVMe drive!" >&2
exit 1
fi
The script performs three checks:
- Ensures root execution (
EUID -eq 0). - Confirms target NVMe capacity exceeds current root utilization (
df -kvs.blockdev --getsize64). - Establishes an
EXIT ERR INT TERMtrap to cleanly unmount temporary working directories if aborted.
Step 2: Driver Injection into Initramfs
Checking the Dragonwing kernel configuration (/boot/config-$(uname -r)):
CONFIG_PCIE_QCOM=y
CONFIG_BLK_DEV_NVME=m
CONFIG_NVME_CORE=m
- The Qualcomm PCIe controller (
CONFIG_PCIE_QCOM) is built directly into the kernel (=y), meaning the PCIe bus initializes automatically during early boot. - The NVMe driver (
CONFIG_BLK_DEV_NVME) is modular (=m).
If nvme and nvme_core are not present in the initramfs image, the kernel will fail to detect the drive before the root pivot, dropping into a BusyBox rescue shell.
MODULES_TO_ADD=("nvme" "nvme_core")
for mod in "${MODULES_TO_ADD[@]}"; do
if ! grep -qs "^${mod}$" /etc/initramfs-tools/modules; then
echo "$mod" >> /etc/initramfs-tools/modules
fi
done
update-initramfs -u -k all
Step 3: Partitioning with Flash Alignment (1 MiB)
Automated partitioning scripts often specify 0% as the partition start offset:
# Antipattern:
parted -s "$NVME_DEV" mkpart primary ext4 0% 100%
On NVMe media, 0% places the first partition at logical sector 34. This violates the 4K physical page boundary and creates performance-degrading read-modify-write cycles.
Instead, align explicitly to 1MiB (sector 2048):
wipefs -a "$NVME_DEV"
parted -s "$NVME_DEV" mklabel gpt
parted -s -a optimal "$NVME_DEV" mkpart "$PART_LABEL" ext4 1MiB 100%
udevadm settle
mkfs.ext4 -F -L "$PART_LABEL" "$NVME_PART"
Step 4: Live Rootfs Replication via Non-Recursive Bind Mount
Cloning a running Linux install must omit virtual pseudofilesystems (/proc, /sys, /dev), temporary runtimes (/run), and mounted eMMC sub-partitions (/boot/efi, /var/persist).
We achieve this with a non-recursive bind mount combined with filesystem-isolated rsync:
mkdir -p "$MNT_SSD" "$MNT_CUR"
mount "$NVME_PART" "$MNT_SSD"
mount --bind / "$MNT_CUR"
rsync -axHAWX --numeric-ids --info=progress2 "$MNT_CUR/" "$MNT_SSD/"
mount --bind(Non-Recursive): Sub-mount points (/proc,/sys,/boot/efi,/var/persist) appear only as empty directory stubs inside$MNT_CUR.-x(--one-file-system): Preventsrsyncfrom crossing filesystem boundaries.-aHAWX: Retains ownership, hard links, symlinks, POSIX ACLs, and extended attributes (file capabilities and SELinux contexts).--numeric-ids: Prevents userspace translation mismatches during file copying.
After synchronization, we recreate clean mount points on the target filesystem:
mkdir -p "$MNT_SSD"/{dev,proc,sys,run,tmp,mnt,media,boot/efi,var/persist}
chmod 1777 "$MNT_SSD/tmp"
Step 5: Updating /etc/fstab on the Target SSD
The source /etc/fstab on eMMC uses label-based referencing:
LABEL=writable / ext4 discard,errors=remount-ro 0 1
LABEL=system-boot /boot/efi/ vfat defaults 0 1
LABEL=persist /var/persist ext4 defaults 0 2
On the SSD, we rewrite / to target the new NVMe UUID while leaving /boot/efi and /var/persist pointed to the eMMC labels:
sed -i "s|^LABEL=writable.*|UUID=${NEW_UUID} / ext4 discard,errors=remount-ro,noatime 0 1|" "$MNT_SSD/etc/fstab"
This ensures that upon booting into NVMe, hardware calibration data and the UEFI partition remain mapped to their original eMMC locations.
Step 6: GRUB Configuration & Drop-In Priority Order
In /etc/default/grub.d/, Dragonwing provides hardware configurations:
99-dragonwing-defaults.cfg: Definesconsole=ttyMSM0,115200n8,earlycon, andpcie_pme=nomsi.99-dragonwing-disable-lvds.cfg: Sets panel options.
Caution: Setting
GRUB_CMDLINE_LINUX="..."directly overwrites these parameters. Losingpcie_pme=nomsican trigger PCIe MSI interrupt storms that stall Qualcomm root complexes.
To handle this cleanly:
- Name the configuration file
zz-nvme-root.cfgso it evaluates after the99-*.cfgfiles in ASCII order. - Append to the variable rather than reassigning it:
cat << 'EOF' > /etc/default/grub.d/zz-nvme-root.cfg
# Dragonwing NVMe root handoff
GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} root=UUID=__TARGET_UUID__ rootdelay=2"
EOF
sed -i "s|__TARGET_UUID__|${NEW_UUID}|" /etc/default/grub.d/zz-nvme-root.cfg
update-grub
Why multiple root= entries resolve correctly
The generated command line in /boot/grub/grub.cfg will look similar to:
linux /boot/vmlinuz-... root=UUID=<eMMC-UUID> ro console=ttyMSM0... root=UUID=<NVME-UUID> rootdelay=2
In Linux and Debian/Ubuntu initramfs-tools, command line parameters are evaluated sequentially from left to right. When multiple root= targets are passed, the last entry takes precedence. rootdelay=2 provides an extra 2-second buffer for the PCIe controller and NVMe namespace to finish initialization before the root pivot.
4. Understanding GRUB Warnings during update-grub
During update-grub, the console may display repeated warnings like:
grub-probe: warning: Discarding improperly nested partition (hostdisk//dev/mmcblk0,gpt68,gpt1).
This is expected behavior on Qualcomm platforms:
- The eMMC contains over 70 partitions. Partition 68 (
mmcblk0p68) is a container partition wrapping nested sub-partitions for internal baseband/modem images. - When
grub-probeinspects block devices, it flags the nested sub-tables and discards them from OS detection. - As long as the command outputs
donewith an exit code of0, the boot configuration has been written successfully.
5. Verification & Rollback Procedure
Reboot the board once the script finishes:
sudo reboot
Verifying the Migration
Run:
findmnt /
You should see:
TARGET SOURCE FSTYPE OPTIONS
/ /dev/nvme0n1p1 ext4 rw,noatime,discard,errors=remount-ro
Run df -h / to confirm the root filesystem reflects your SSD’s total capacity.
Emergency Rollback (eMMC Fallback)
Because /dev/mmcblk0p71 was never modified or erased, you can revert instantly:
- From the OS:
sudo rm /etc/default/grub.d/zz-nvme-root.cfg sudo update-grub sudo reboot - From the GRUB Menu (Serial/Display):
Interrupt the 3-second GRUB boot counter, press
eto edit the kernel line, deleteroot=UUID=... rootdelay=2, and pressCtrl+Xto boot directly into eMMC.
6. Long-Term Maintenance: Kernel Updates
Because the initial kernel binary (/boot/vmlinuz) is loaded from the eMMC by GRUB, standard kernel updates require awareness:
- On the NVMe system,
/boot/efimaps back to the eMMC EFI partition viaLABEL=system-boot. - However,
/bootitself resides on the NVMe filesystem. Ifapt upgradeinstalls a new kernel image, it writes the new files to the NVMe drive’s/boot/path. - Because the eMMC’s
/bootholds the kernel that GRUB loads, you should periodically sync the updated kernel and initramfs binaries back to the eMMC partition when performing major kernel updates:
# Mount eMMC root to sync kernel binaries after an upgrade
sudo mkdir -p /mnt/emmc
sudo mount /dev/mmcblk0p71 /mnt/emmc
sudo rsync -av /boot/ /mnt/emmc/boot/
sudo umount /mnt/emmc
Appendix A: Hardware Context & I/O Constraints
When selecting external storage and peripherals for the Dragonwing / Ventuno Q platform, keep these hardware characteristics in mind:
USB-C Speed vs. DisplayPort Alt-Mode
The onboard USB-C port is managed by a Cypress CYPD6129 PD controller and a Texas Instruments TMUXHS4446RETT multiplexer. The high-speed differential pairs are shared dynamically:
| Mode | SuperSpeed Lanes Used By | Effective USB Data Speed |
|---|---|---|
| Data Only (No External Display) | USB 3.0 Controller | 5 Gbps (USB 3.0 / 3.2 Gen 1x1) |
| DisplayPort Alt-Mode Active | DisplayPort Video Output | 480 Mbps (USB 2.0 HighSpeed fallback) |
If you rely on external high-speed storage over USB-C while running a display via DP Alt-Mode, data rates will drop to USB 2.0 speeds. Moving your primary operating system to an internal M.2 NVMe SSD keeps high-speed storage independent of display state.
Appendix B: The Complete Migration Script
#!/usr/bin/env bash
set -euo pipefail
# --- CONFIGURATION TARGETS ---
NVME_DEV="/dev/nvme0n1"
NVME_PART="/dev/nvme0n1p1"
PART_LABEL="nvme_root"
MNT_SSD="/mnt/ssd_root"
MNT_CUR="/mnt/current_root"
echo "=========================================================="
echo " Qualcomm Dragonwing / Ventuno Q: NVMe Rootfs Migration"
echo " (Safe Kernel Handoff Architecture)"
echo "=========================================================="
# 1. PRE-FLIGHT CHECKS
if [ "$EUID" -ne 0 ]; then
echo "[-] ERROR: This script must be run as root (use sudo)." >&2
exit 1
fi
CURRENT_ROOT=$(findmnt -n -o SOURCE /)
if [[ "$CURRENT_ROOT" == *"nvme"* ]]; then
echo "[-] ERROR: Your system is ALREADY running from an NVMe drive ($CURRENT_ROOT)!" >&2
exit 1
fi
if [ ! -b "$NVME_DEV" ]; then
echo "[-] ERROR: Target SSD '$NVME_DEV' not found! Check your M.2 installation." >&2
exit 1
fi
ROOT_USED_KB=$(df -k --output=used / | tail -n 1 | tr -d ' ')
DEV_SIZE_BYTES=$(blockdev --getsize64 "$NVME_DEV")
DEV_SIZE_KB=$((DEV_SIZE_BYTES / 1024))
if [ "$DEV_SIZE_KB" -le "$ROOT_USED_KB" ]; then
echo "[-] ERROR: Target SSD is too small ($((DEV_SIZE_KB / 1024 / 1024)) GB) for current root usage ($((ROOT_USED_KB / 1024 / 1024)) GB)!" >&2
exit 1
fi
echo "[+] Current Root is on: $CURRENT_ROOT"
echo "[+] Target NVMe Drive is: $NVME_DEV ($((DEV_SIZE_BYTES / 1024 / 1024 / 1024)) GB)"
echo ""
read -r -p "WARNING: ALL DATA ON $NVME_DEV WILL BE ERASED. Continue? (y/N): " CONFIRM
if [[ ! "$CONFIRM" =~ ^[yY]$ ]]; then
echo "[-] Aborted by user."
exit 0
fi
# Cleanup trap for safe interruption
cleanup() {
echo "[*] Cleaning up temporary mounts..."
umount "$MNT_CUR" 2>/dev/null || true
umount "$MNT_SSD" 2>/dev/null || true
rmdir "$MNT_CUR" 2>/dev/null || true
rmdir "$MNT_SSD" 2>/dev/null || true
}
trap cleanup EXIT ERR INT TERM
# 2. ENSURE INITRAMFS HAS NVME MODULES
echo ""
echo "[1/5] Ensuring NVMe driver is in initramfs..."
mkdir -p /etc/initramfs-tools
MODULES_TO_ADD=("nvme" "nvme_core")
for mod in "${MODULES_TO_ADD[@]}"; do
if ! grep -qs "^${mod}$" /etc/initramfs-tools/modules; then
echo "$mod" >> /etc/initramfs-tools/modules
echo "[+] Added '$mod' to /etc/initramfs-tools/modules"
fi
done
update-initramfs -u -k all
# 3. PARTITION & FORMAT NVME SSD
echo ""
echo "[2/5] Partitioning and formatting $NVME_DEV..."
swapoff -a 2>/dev/null || true
umount "${NVME_DEV}"* 2>/dev/null || true
wipefs -a "$NVME_DEV"
parted -s "$NVME_DEV" mklabel gpt
parted -s -a optimal "$NVME_DEV" mkpart "$PART_LABEL" ext4 1MiB 100%
udevadm settle
partprobe "$NVME_DEV" || true
sleep 1
mkfs.ext4 -F -L "$PART_LABEL" "$NVME_PART"
udevadm settle
NEW_UUID=$(blkid -s UUID -o value "$NVME_PART")
if [ -z "$NEW_UUID" ]; then
echo "[-] ERROR: Failed to obtain UUID for $NVME_PART." >&2
exit 1
fi
echo "[+] Formatted $NVME_PART with UUID: $NEW_UUID"
# 4. CLONE ROOTFS TO SSD
echo ""
echo "[3/5] Cloning rootfs to NVMe SSD..."
mkdir -p "$MNT_SSD" "$MNT_CUR"
mount "$NVME_PART" "$MNT_SSD"
mount --bind / "$MNT_CUR"
rsync -axHAWX --numeric-ids --info=progress2 "$MNT_CUR/" "$MNT_SSD/"
mkdir -p "$MNT_SSD"/{dev,proc,sys,run,tmp,mnt,media,boot/efi,var/persist}
chmod 1777 "$MNT_SSD/tmp"
umount "$MNT_CUR"
rmdir "$MNT_CUR"
# 5. UPDATE FSTAB ON SSD
echo ""
echo "[4/5] Updating /etc/fstab on the SSD..."
sed -i "s|^LABEL=writable.*|UUID=${NEW_UUID} / ext4 discard,errors=remount-ro,noatime 0 1|" "$MNT_SSD/etc/fstab"
echo "[+] New /etc/fstab contents on SSD:"
cat "$MNT_SSD/etc/fstab"
umount "$MNT_SSD"
rmdir "$MNT_SSD"
# 6. CONFIGURE GRUB ON eMMC FOR KERNEL HANDOFF
echo ""
echo "[5/5] Configuring GRUB kernel handoff on eMMC..."
mkdir -p /etc/default/grub.d
cat << 'EOF' > /etc/default/grub.d/zz-nvme-root.cfg
# Dragonwing NVMe root handoff
GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} root=UUID=__TARGET_UUID__ rootdelay=2"
EOF
sed -i "s|__TARGET_UUID__|${NEW_UUID}|" /etc/default/grub.d/zz-nvme-root.cfg
echo "[+] Generated /etc/default/grub.d/zz-nvme-root.cfg:"
cat /etc/default/grub.d/zz-nvme-root.cfg
echo ""
echo "[*] Updating GRUB..."
update-grub
sync
trap - EXIT ERR INT TERM
echo ""
echo "=========================================================="
echo " [SUCCESS] Migration complete!"
echo " The EFI stub remains safe on eMMC, and the kernel"
echo " will hand over rootfs to your NVMe SSD on boot."
echo " Reboot now to boot from your NVMe SSD."
echo "=========================================================="