Skip to content

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_64 available
  • socat installed 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-ota in DISTRO_FEATURES
  • For USB updates: image built with update-local in DISTRO_FEATURES
  • See Configure Update Modes for how to enable these features
  • The signed .swu package built and available locally

Build the Update Package

Build a full SWUpdate package from inside the devcontainer:

kas build kas/tolomeo-qemux86-64_imgen-update-full.yml

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:

mkdir -p shared

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:

ssh -p 2222 admin@127.0.0.1

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:

nats sub "events.params"

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:

fw_printenv upgrade_available

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:

chmod +x hotplug-usb.sh
./hotplug-usb.sh

Monitor Update

Connect to the running VM via SSH to watch the update progress:

ssh -p 2222 admin@127.0.0.1

Inside the guest, follow the swupdate service log:

journalctl -fu 'swupdate-usb@*'

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:

fw_printenv upgrade_available

The value should be unset once the new image has booted and confirmed itself healthy.