Test Updates in QEMU¶
This guide covers triggering OTA streaming and USB updates against a QEMU instance. Both methods share the same prerequisites and QEMU setup.
Prerequisites¶
- QEMU installed with
qemu-system-x86_64available socatinstalled on the host (required for USB simulation)- QEMU boot artifacts already available (see
meta-tolomeo-qemu/README.md) - For OTA streaming: image built with
update-otainDISTRO_FEATURES - For USB updates: image built with
update-localinDISTRO_FEATURES - See Configure Update Modes for how to enable these features
- The signed
.swupackage built and available locally
Build the Update Package¶
Build a full SWUpdate package from inside the devcontainer:
The recipe uses the stable,update software selection and targets /dev/vdb via GPT partition swap.
After the build completes, the .swu package is collected under:
artifacts/tolomeo-qemux86-64/image-prod/<version>/updates/imgen-update-full/
└── imgen-update-full-tolomeo-qemux86-64.rootfs.swu
Upload the package to the device management platform and mark it as a new release for the target device before proceeding.
Prepare the Working Directory¶
Your working directory should contain the following before launching QEMU:
.
├── bzImage
├── image-prod-tolomeo-qemux86-64.rootfs.ext4
├── qemu-disk.img # provisioned disk with certificates
└── shared/ # local directory for shared mount
Create the shared directory if it does not exist:
Run QEMU¶
The QEMU command for OTA updates is identical to the standalone mode guide — no USB-specific flags are required. Launch the VM:
qemu-system-x86_64 \
-kernel bzImage \
-append 'root=/dev/vda rw mem=1024M ip=dhcp console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0' \
-drive file=image-prod-tolomeo-qemux86-64.rootfs.ext4,if=virtio,format=raw \
-drive file=qemu-disk.img,if=virtio,format=raw,id=qemu-disk \
-virtfs local,path=shared,mount_tag=shared,security_model=mapped-xattr \
-device virtio-net-pci,netdev=net0,mac=52:54:00:12:35:02 \
-netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:2323-:23 \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0 \
-cpu IvyBridge -machine q35,i8042=off \
-smp 4 -m 1024 \
-usb -device usb-tablet -usb -device usb-kbd \
-serial mon:stdio -serial null -nographic
KVM acceleration
Add -enable-kvm for better performance. This may require sudo depending
on your system configuration.
OTA Streaming Update¶
Trigger the OTA Update¶
Once the VM has booted, connect via SSH from a second terminal:
Use natscli inside the guest to interact with the swupdate management service. Run GetOTAStatus
first to confirm the update is visible to the device before proceeding.
Check update availability:
timestamp=$(date +%s.%3N)
nats pub commands.swupdate.req \
'[{"bn":"", "t":'"$timestamp"',"n":"GetOTAStatus","vs":"{\"id\":\"swupdate\"}"}]'
Start the download:
timestamp=$(date +%s.%3N)
nats pub commands.swupdate.req \
'[{"bn":"", "t":'"$timestamp"',"n":"StartOTADownload","vs":"{\"id\":\"swupdate\"}"}]'
Install the downloaded update:
timestamp=$(date +%s.%3N)
nats pub commands.swupdate.req \
'[{"bn":"", "t":'"$timestamp"',"n":"InstallOTAUpdate","vs":"{\"id\":\"swupdate\"}"}]'
Monitor Update¶
In the same SSH session (or a parallel one), subscribe to the events topic to follow the status progression in real time:
A typical successful run produces the following sequence of events:
[#1] Received on "events.params"
[{"bn": "urn:cpt:device:sn:TVD_0000:", "n": "GetOTAStatus", "vs": "{\"id\": \"swupdate\", \"result\": \"success\", \"status\": \"update_available\"}"}]
[#2] Received on "events.params"
[{"bn": "urn:cpt:device:sn:TVD_0000:", "n": "StartOTADownload", "vs": "{\"id\": \"swupdate\", \"result\": \"success\", \"status\": \"downloading\"}"}]
[#3] Received on "events.params"
[{"bn": "urn:cpt:device:sn:TVD_0000:", "n": "GetOTAStatus", "vs": "{\"id\": \"swupdate\", \"result\": \"success\", \"status\": \"update_ready\"}"}]
[#4] Received on "events.params"
[{"bn": "urn:cpt:device:sn:TVD_0000:", "n": "InstallOTAUpdate", "vs": "{\"id\": \"swupdate\", \"result\": \"success\", \"status\": \"updating\"}"}]
After the updating event the VM reboots into the new image. Once it comes back up, confirm the
rollback guard was cleared:
The value should be unset once the new image has booted and confirmed itself healthy.
USB Update¶
Prepare USB Storage¶
The .swu package must be placed at the root of a FAT filesystem on a USB storage device. Use either
a real USB stick or a disk image file.
Option A — real USB stick:
Copy the package to the root of the USB stick:
cp artifacts/tolomeo-qemux86-64/image-prod/<version>/updates/imgen-update-full/imgen-update-full-tolomeo-qemux86-64.rootfs.swu /media/<mount>/
sync
Option B — disk image file:
Create a FAT image and copy the package onto it:
dd if=/dev/zero of=usb-stick.img bs=1M count=128
mkfs.vfat usb-stick.img
mcopy -i usb-stick.img \
artifacts/tolomeo-qemux86-64/image-prod/<version>/updates/imgen-update-full/imgen-update-full-tolomeo-qemux86-64.rootfs.swu \
::imgen-update-full-tolomeo-qemux86-64.rootfs.swu
Simulate USB Insertion¶
Once the VM has fully booted, run the following script on the host to hot-plug the USB storage device
into the guest. QEMU exposes it as a USB mass storage device and the guest udev rules fire
swupdate-usb@<device>.service automatically.
For USB updates, launch QEMU with the monitor socket and USB storage backend declared. The -drive if=none option makes
the storage available to QEMU without exposing it to the guest immediately — the hot-plug step below attaches
it as a USB device at runtime.
Replace usb-stick.img with your real block device path (e.g. /dev/sde) if using a physical USB stick.
qemu-system-x86_64 \
-kernel bzImage \
-append 'root=/dev/vda rw mem=1024M ip=dhcp console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0' \
-drive file=image-prod-tolomeo-qemux86-64.rootfs.ext4,if=virtio,format=raw \
-drive file=qemu-disk.img,if=virtio,format=raw,id=qemu-disk \
-drive file=usb-stick.img,if=none,id=usb-stick,format=raw \
-virtfs local,path=shared,mount_tag=shared,security_model=mapped-xattr \
-device virtio-net-pci,netdev=net0,mac=52:54:00:12:35:02 \
-netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:2323-:23 \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0 \
-cpu IvyBridge -machine q35,i8042=off \
-smp 4 -m 1024 \
-usb -device usb-tablet -usb -device usb-kbd \
-monitor unix:/tmp/qemu-monitor.sock,server,nowait \
-serial mon:stdio -serial null -nographic
KVM acceleration
Add -enable-kvm for better performance. This may require sudo depending
on your system configuration.
Real block device access
Passing a physical block device (e.g. /dev/sde) requires read access
to that device, which typically requires sudo.
Once QEMU is running, hot-plug the USB storage device using the following script:
#!/bin/bash
SOCK=/tmp/qemu-monitor.sock
# Wait for the QEMU monitor socket to become available
echo "Waiting for QEMU monitor socket..."
until [ -S "$SOCK" ]; do sleep 1; done
echo "Monitor ready."
# Wait for the guest to finish booting before hot-plugging.
# Increase this value if udev does not fire — the guest may still be initialising.
sleep 15
echo "Hot-plugging USB stick..."
echo 'device_add usb-storage,drive=usb-stick,id=stick0' | socat - "UNIX-CONNECT:$SOCK"
echo "Done. udev should fire swupdate-usb@<device>.service inside the guest."
Save the script (e.g. as hotplug-usb.sh), make it executable, and run it from a second terminal while QEMU is running:
Monitor Update¶
Connect to the running VM via SSH to watch the update progress:
Inside the guest, follow the swupdate service log:
A successful update writes upgrade_available=1 to the boot environment and reboots the VM into the
new image. After the VM restarts, confirm the rollback guard was cleared:
The value should be unset once the new image has booted and confirmed itself healthy.