1. Overview: Why Virtualize the SIMATIC IOT2040
The Siemens SIMATIC IOT2040 is an industrial IoT gateway based on the Intel Quark x1020 SoC (32-bit x86, single core @ 400 MHz) with 1 GB DDR3 RAM, two Gigabit Ethernet ports, two USB 2.0 ports, RS232/485, an Arduino Uno R3-compatible shield interface, and a mini-PCIe slot. The device ships with a custom Yocto-based Linux image (Example Image 2.4.x and later) that boots through a 32-bit UEFI firmware. The same image is distributed as a .wic file intended to be flashed to the on-board eMMC via dps or a host-side dd routine.
Virtualizing the gateway offers three practical benefits during development:
- CI/CD smoke testing of Node-RED, PLCnext, MQTT, OPC UA, or custom C/C++ applications before deploying to physical hardware.
-
Application debugging with
gdb-multiarch,strace, and breakpoints that survive a VM snapshot. - Training and demo on engineering workstations without consuming a physical IOT2040 unit.
The trade-off is that the IOT2040 is not a standard PC platform. The Intel Quark UEFI is 32-bit, the kernel is built for the pentium target, and the on-board peripherals (Arduino shield GPIOs, RS485 transceiver, mini-PCIe signals) have no native counterpart in VMware Workstation, VirtualBox, or QEMU. The virtual machine is therefore a logical sandbox — the network plane, the user-space runtime, and the systemd services work; the hardware I/O plane does not.
2. Hardware and Firmware Constraints You Must Respect
| Parameter | IOT2040 Value | VM Implication |
|---|---|---|
| CPU architecture | Intel Quark x1020, 32-bit, single core | Use qemu-system-i386 (or x86_64 with -cpu pentium); create a 32-bit firmware VM |
| UEFI class | 32-bit UEFI (Quark EDK II build) | Requires IA32 OVMF, not the default X64 OVMF shipped with most hypervisors |
| Boot loader | GRUB2-efi (IA32) loading a vmlinuz with pentium target |
Kernel must be i386/i586; 64-bit-only kernels will not boot |
| RAM | 1 GB DDR3 (soldered) | Allocate 1024 MB minimum in the VM; 1536 MB is comfortable for Node-RED |
| Storage | 4 GB eMMC on-board | Mount the .wic as a raw disk; expect two partitions (EFI + rootfs) |
| Network | 2× Intel i210 / i211 GbE | Map eth0/eth1 with virtio-net only; E1000 driver exists in IOT2000 kernel but is not always enabled |
| USB | 2× USB 2.0 host | USB passthrough works in QEMU and VMware; ignored in most workflows |
| Arduino shield I/O | SPI, I2C, ADC, GPIO via Quark SoC | No emulation possible in any hypervisor; this is the blocking constraint for hardware integration |
The single most common reason an IOT2040 image refuses to boot inside a VM is the 32-bit UEFI mismatch. Modern VMware Workstation 15 and VirtualBox 6.x both default to 64-bit UEFI (OVMF X64) or to a 64-bit Hyper-V-compatible firmware, and the IOT2040's 32-bit bootloader cannot be loaded by them. A 32-bit IA32 OVMF firmware blob is required.
3. Understanding the .wic Image
The .wic file is produced by the Yocto wic tool. It is a raw disk image (not a compressed archive) that you can mount, partition, and inspect without writing it to a real device. For the IOT2000 SDK, the layout is:
image-iot2040.wic
├── Partition 1: EFI System Partition (FAT16/32, ~32 MB, type 0xEF)
│ └── EFI/BOOT/BOOTIA32.EFI (the 32-bit GRUB2 EFI binary)
└── Partition 2: rootfs (ext4, type 0x83, fills remaining space)
└── /boot/vmlinuz*, /boot/grub/grub.cfg, /lib/modules/...
Inspect it on Linux before launching the VM:
$ fdisk -l image-iot2040.wic
Disk image-iot2040.wic: 1.4 GiB, 1500000000 bytes, 2930000 sectors
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
image-iot2040.wic1 * 0,32,33 16,81,13 2048 67583 65536 32M ef EFI (FAT-12/16/32)
image-iot2040.wic2 16,81,14 1023,81,51 67584 2929663 2862080 1.4G 83 Linux
Loop-mount the rootfs for offline editing (handy for injecting SSH keys or a static network config):
$ sudo losetup -f --show -P image-iot2040.wic
/dev/loop0
$ sudo mount /dev/loop0p2 /mnt/iot
$ ls /mnt/iot/boot
vmlinuz-5.10.0-iot grub System.map-5.10.0-iot
$ sudo umount /mnt/iot
$ sudo losetup -d /dev/loop0
.wic as a raw disk to a VM that defaults to GPT — VMware and VirtualBox will still boot from MBR, but Windows-side tools that assume GPT may misread the geometry.
4. Prerequisites
Before launching any of the methods below, gather the following:
- The IOT2040 example image, e.g.
image-iot2040.wic(image 2.4 or later), from the official Siemens Industry Online Support portal at support.industry.siemens.com. - The Siemens IOT2000 SDK sources if you intend to rebuild a custom image: github.com/siemens/iot2000.
- An IA32 (32-bit) UEFI firmware blob. The
edk2-stable202211or later tag containsOvmfPkg/Ia32/Ia32X64.fd; for the Quark target, the binaryOVMF-IA32.fdfrom theiot2000-setupproject is a known-good choice. The 32-bitOVMF_CODE_IA32.fddistributed by the tianocore/edk2 repository is the canonical source. - QEMU 6.x or later with
qemu-system-i386and theedk2-ovmf-ia32package on Linux. - VMware Workstation 15.5.7 or later (15.x) on Windows, or Workstation Pro 16.x/17.x.
- VirtualBox 6.1.x or 7.0.x with the Extension Pack installed.
Verify the firmware blob is IA32 by checking the PE/COFF header:
$ file OVMF-IA32.fd
OVMF-IA32.fd: MS-DOS executable PE32 executable (EFI) Intel 80386 (DLL), for MS Windows
The Intel 80386 (not x86-64) string is the confirmation you need.
5. Method 1 — QEMU on Linux (Recommended)
QEMU gives the cleanest path because it can load an arbitrary 32-bit OVMF firmware, emulates a generic virtio disk, and exposes a virtio-net-pci device that the IOT2000 kernel recognizes. The Yocto build system also bundles a wrapper script, runqemu, that handles most of the arguments for you.
5.1 Quick path with runqemu
From the Yocto build directory of a built IOT2000 image:
$ source oe-init-build-env
$ runqemu nographic wic
runqemu - INFO - Running /usr/bin/qemu-system-i386...
...
runqemu automatically selects qemu-system-i386, attaches the .wic as a virtio-blk device, and brings up a usermode network. If you want KVM acceleration, your host kernel must expose kvm-intel; the Quark CPU itself is 32-bit but QEMU presents it to a 64-bit KVM process, so KVM works on modern hosts.
5.2 Manual qemu-system-i386 invocation
Use the manual form when you need explicit control of the firmware and network:
qemu-system-i386 \
-machine q35 \
-cpu pentium \
-smp 1 \
-m 1024 \
-drive if=pflash,format=raw,readonly=on,file=OVMF-IA32.fd \
-drive file=image-iot2040.wic,format=raw,if=virtio \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 \
-serial mon:stdio \
-nographic
Argument breakdown:
| Argument | Purpose |
|---|---|
-machine q35 |
Modern chipset with PCIe root; required for virtio-net-pci under a 32-bit firmware |
-cpu pentium |
Matches the Quark x1020 feature set (no PAE required, no SSE3); use pentium2 if you need PAE |
-m 1024 |
Mirrors the physical 1 GB; 1536 is acceptable for development |
-drive if=pflash,... |
Mounts IA32 OVMF as the platform flash; this is what makes UEFI boot possible in a 32-bit guest |
hostfwd=tcp::2222-:22 |
Forwards host port 2222 to guest SSH on 22 for remote access |
-device virtio-net-pci |
Required — the e1000 driver is not always built into the IOT2000 kernel |
-serial mon:stdio |
Routes the IOT2040 serial console to the terminal, useful for boot diagnostics |
5.3 Bridged networking in QEMU
For a VM that needs to reach a real LAN (e.g. to talk to a PLC over Modbus/TCP), bridge tap0 to the host interface:
# On the host (root)
ip tuntap add dev tap0 mode tap
ip link set tap0 up
brctl addif br0 tap0 # br0 already bridges eth0
qemu-system-i386 ... \
-netdev tap,id=net0,ifname=tap0,script=no,downscript=no \
-device virtio-net-pci,netdev=net0 \
-netdev user,id=net1 \
-device virtio-net-pci,netdev=net1
Use the second virtio-net-pci for an out-of-band management network that always works, even if the bridge loses link state.
6. Method 2 — VMware Workstation 15 on Windows
VMware Workstation supports UEFI firmware, but the bundled firmware is 64-bit. There is no built-in option to switch to IA32 firmware. The reliable workaround is to attach the .wic as a raw disk to a 32-bit firmware-aware virtual machine, and to accept that VMware will report a non-standard firmware version.
6.1 Create the VM
- Launch VMware Workstation 15 (or 16/17) and choose Create a New Virtual Machine > Custom (advanced).
- Set Hardware compatibility to Workstation 15.x.
- Choose I will install the operating system later.
- Set Guest OS to Linux > Other Linux 3.x or later kernel 32-bit. The 32-bit selection is critical — it forces VMware to load the IA32 firmware shim instead of the X64 firmware.
- Allocate 1 CPU, 1024 MB RAM.
- Set the network adapter to NAT initially; switch to Bridged once the VM is reachable.
- Skip the virtual disk creation — you will attach the
.wicdirectly.
6.2 Attach the .wic as a raw disk
Create a small seed VMDK descriptor that points at the .wic:
"RW \n" > rawdisk.vmdk
This two-line VMDK is the standard VMware raw-disk mapping trick documented in the VMware Workstation Pro documentation:
# disk.vmdk
# Extent description
RW 2930000 FLAT "image-iot2040.wic" 0
# The Disk Data Base
#DDB
ddb.virtualHWVersion = "14"
ddb.adapterType = "ide"
ddb.geometry.sectors = "63"
ddb.geometry.heads = "16"
ddb.geometry.cylinders = "2906"
Edit the VM's .vmx file and add:
firmware = "efi"
efi.filename = ""
firmwareBootTimeout = "3000"
ide0:0.present = "TRUE"
ide0:0.fileName = "disk.vmdk"
ide0:0.mode = "independent-nonpersistent"
monitor_control.restrict_backdoor = "TRUE"
firmware = "efi" enables UEFI; the empty efi.filename tells Workstation to fall back to its own built-in UEFI rather than an external firmware file. Combined with the 32-bit guest OS type from step 4, this is the closest VMware gets to IA32 UEFI.
6.3 Boot, network, and known issues
- On first boot, the GRUB2 menu from the
.wicappears; choose the default kernel. If you see UEFI Interactive Shell instead, the firmware did not load GRUB. Re-check that the guest OS type is 32-bit Linux. - VMware's
vlance,e1000, andvmxnet3paravirtual NICs may or may not have a driver in the IOT2000 kernel. The safest choice is thee1000NIC:ethernet0.virtualDev = "e1000". If that does not work, switch tovlance(an AMD PCnet clone, 10 Mbit, present in all IOT2000 kernels). - VMware's proprietary vmxnet3 driver is not in the IOT2040 default kernel — do not select it.
- Power off the VM with Suspend > Power Off rather than Reset; the IOT2040 rootfs uses ext4 without journal on small partitions and a hard reset occasionally leaves it inconsistent.
6.4 Practical recipe (Windows command line)
You can launch the VM from vmware.exe -x with the vmx path once it is configured. For scripting, here is a PowerShell snippet that converts a .wic to a vmdk descriptor and patches the vmx:
$wic = "C:\iot\image-iot2040.wic"
$vmx = "C:\iot\iot2040-vm\iot2040-vm.vmx"
$desc = @"
# Extent description
RW 2930000 FLAT "$wic" 0
# The Disk Data Base
#DDB
ddb.virtualHWVersion = "14"
ddb.adapterType = "ide"
ddb.geometry.sectors = "63"
ddb.geometry.heads = "16"
ddb.geometry.cylinders = "2906"
"@
$desc | Out-File "C:\iot\iot2040-vm\disk.vmdk" -Encoding ASCII
Add-Content $vmx "ide0:0.present = `"TRUE`""
Add-Content $vmx "ide0:0.fileName = `"disk.vmdk`""
Add-Content $vmx "ide0:0.mode = `"independent-nonpersistent`""
Add-Content $vmx "ethernet0.virtualDev = `"e1000`""
7. Method 3 — VirtualBox on Windows
VirtualBox 6.1.x and 7.0.x ship with a 64-bit OVMF firmware in the Extension Pack. There is no first-class IA32 OVMF build in the public VirtualBox distribution, which is why you may have observed the VM dropping to the UEFI Interactive Shell and never finding EFI/BOOT/BOOTIA32.EFI. The fix is to inject an IA32 firmware blob into the VM's NVRAM.
7.1 Create the VM
- In VirtualBox Manager, click New and configure:
- Type: Linux
- Version: Other Linux (32-bit)
- Memory: 1024 MB
- Hard disk: Do not add a virtual hard disk (you will attach a raw disk next)
- Open the VM settings, go to System > Motherboard, and tick Enable EFI (special OSes only). In VirtualBox 7.0 this is in System > EFI.
- Go to Storage > Controller: IDE and click the second hard-disk icon. Choose Choose existing disk and browse to a small VMDK that points at the
.wic(the same descriptor used in the VMware recipe works). - Under Network > Adapter 1, set Attached to: Bridged Adapter (or NAT during initial bring-up). Set Adapter Type: Intel PRO/1000 MT Desktop (82540EM). The
virtio-netadapter type is exposed by VirtualBox 6.1+ but the IOT2000 image has novirtio-netdriver compiled in, so use 82540EM.
7.2 Replace the firmware with an IA32 OVMF
VirtualBox stores NVRAM as a per-VM file with a variable-size VBoxVDI/VBoxNV format. To substitute IA32 OVMF:
- Download a 32-bit OVMF firmware blob (e.g.
OVMF-IA32.fdfrom the tianocore github.com/tianocore/edk2 project). - With the VM powered off, copy the blob into the VM folder:
VirtualBox VMs\iot2040-vm\OVMF-IA32.fd. - Edit the
iot2040-vm.vboxXML and add to the<ExtraData>section:<ExtraDataItem name="VBoxInternal/Devices/pcbios/0/Config/BiosRom" value="OVMF-IA32.fd"/>
On next start, VirtualBox flashes the custom firmware, the GRUB2 BOOTIA32.EFI is found, and the IOT2040 kernel boots normally. If the boot still falls to the UEFI Interactive Shell, list the file system in the shell:
Shell> fs0:
FS0:\> ls EFI\BOOT
BOOTIA32.EFI
If the file is present, manually launch it: FS0:\EFI\BOOT\BOOTIA32.EFI. If the file is not present, the .wic EFI partition is not mounted — the firmware is X64 and cannot read the FAT partition with 32-bit cluster hints. Re-do step 7.2 with a true IA32 OVMF.
7.3 Known VirtualBox quirks
- VirtualBox's virtio-net adapter presents a PCI vendor/device ID of
1af4:1000; the IOT2040 kernel includes thevirtio_netmodule but the IOT2000 defaultimage-iot2040.wicfrom Siemens may have it compiled as a module, not loaded automatically. If you needvirtio-net, addvirtio_netto/etc/modules-load.d/iot2000.confon the loop-mounted rootfs before booting. - The IOT2040 expects
eth0andeth1interface names. VirtualBox's 82540EM adapter enumerates asenp0s3under modern systemd's predictable naming. Disable predictable naming by appendingnet.ifnames=0 biosdevname=0to the kernel command line in/boot/grub/grub.cfg. - VirtualBox Guest Additions are not compatible with the IOT2040 kernel build and must not be installed. Sharing folders with the host is best done through a host-side
smbornfsshare instead.
8. Networking in the Virtual IOT2040
Whether you use QEMU, VMware, or VirtualBox, the IOT2040's user-space network configuration expects DHCP on eth0 and a static fallback on eth1. The default /etc/systemd/network/10-eth.network in image 2.4 looks like:
[Match]
Name=eth0
[Network]
DHCP=yes
IPv6AcceptRA=yes
[DHCPv4]
UseHostname=no
If you want the VM to be reachable from the host without DHCP, set a static address in /etc/systemd/network/20-eth-static.network:
[Match]
Name=eth1
[Network]
Address=192.168.7.2/24
Gateway=192.168.7.1
DNS=1.1.1.1
On the host (Linux), set the bridge address to 192.168.7.1/24 with ip addr add 192.168.7.1/24 dev tap0 after starting the QEMU VM. On Windows, the equivalent is a static address on the VirtualBox Host-Only Ethernet Adapter or the VMware VMnet1 interface.
8.1 Verifying the network plane
# Inside the VM, after boot
$ ip -br addr
eth0 UP 192.168.7.2/24
eth1 DOWN
$ ping -c 3 192.168.7.1
PING 192.168.7.1 (192.168.7.1): 56 data bytes
64 bytes from 192.168.7.1: seq=0 ttl=64 time=0.4 ms
...
$ systemctl status systemd-networkd
● systemd-networkd.service - Network Service
Active: active (running) since ...
virtio-net configurations default to an MTU of 1500, while the IOT2000's real Intel i210 negotiates 9000 (jumbo frames) if the link partner supports it. If your application makes heavy use of OPC UA binary protocol or large Modbus frames, leave the VM MTU at 1500 to match what production code will see on a typical 1 Gbit industrial switch.
9. I/O Limitations: What Cannot Be Virtualized
This is the single most important fact for a project manager: an IOT2040 VM cannot exercise the Arduino shield, RS485, the user LEDs, or the mini-PCIe slot. The Intel Quark SoC multiplexes GPIO, SPI, I2C, and UART into the Arduino Uno R3 connector through a custom firmware; none of this is emulated by QEMU, VMware, or VirtualBox. The application code that calls mraa, libiot2000, or accesses /sys/class/gpio will see no devices.
Concretely:
| IOT2040 Feature | Available in VM? | Alternative |
|---|---|---|
Ethernet eth0/eth1
|
Yes (with virtio-net or e1000) |
Use as in production |
| USB 2.0 host | Yes (QEMU/VMware passthrough) | Use as in production |
| RS232 console | Yes (QEMU -serial, VMware serial pipe) |
Use as in production |
| RS485 half-duplex | No | Test with a USB-to-RS485 dongle attached via passthrough |
| Arduino shield SPI/I2C/ADC/GPIO | No | Stub with a Python fake; unit-test the application logic separately |
| Mini-PCIe (cellular, Wi-Fi) | No | Use a USB cellular or Wi-Fi dongle |
| User LEDs (D5/D6 on the shield) | No | Visible in production; do not branch application logic on them |
| Real-time clock (PCF2127) | No | VM uses the host RTC; expect time skew during suspend |
| Watchdog (Quark internal) | No | Stops the kernel; ignore in CI |
10. Verification — Proving the VM Is Functional
After the VM is running, walk through this checklist before declaring success:
- Boot phase: GRUB2 menu appears within 5 s of UEFI hand-off. If the firmware drops you into a UEFI shell, the firmware is not IA32.
-
Kernel phase: the Quark-targeted kernel prints
Linux version 5.10.0-iot ... (gcc version ...)and the Intel Quark SoC driver probes succeed. -
systemd phase:
systemd --failedshows zero units. The most common failure issystemd-networkd-wait-online.servicetiming out before DHCP completes; if that happens, increaseTimeoutStartSec=60sin the unit override. -
Network phase:
ip -br addrshows an IPv4 address oneth0within 30 s;ping 8.8.8.8succeeds from the VM. -
User space: the IOT2000 SDK's
iot2000setuptool reports Platform detected: SIMATIC IOT2040 — this is a strong signal that user-space sees a coherent gateway, even if the on-board peripherals are not present. - Application phase: your Node-RED, PLCnext, or custom daemon starts and serves its protocol (MQTT, OPC UA, Modbus/TCP) on the expected port.
Capture each of these as a CI gate in Jenkins or GitLab CI. A complete QEMU-based smoke test for a hypothetical MQTT gateway looks like this:
#!/bin/bash
set -e
qemu-system-i386 -machine q35 -cpu pentium -m 1024 \
-drive if=pflash,format=raw,readonly=on,file=OVMF-IA32.fd \
-drive file=image-iot2040.wic,format=raw,if=virtio \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
-serial mon:stdio \
-nographic -daemonize -pidfile /tmp/iot.pid
# Wait for the guest to come up
for i in {1..30}; do
sleep 1
nc -z 127.0.0.1 1883 && break
done
# Smoke test
mosquitto_pub -h 127.0.0.1 -p 1883 -t test -m "ci-ok"
test "$(mosquitto_sub -h 127.0.0.1 -p 1883 -t test -C 1 -W 5)" = "ci-ok"
kill "$(cat /tmp/iot.pid)"
rm -f /tmp/iot.pid
11. Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
UEFI Interactive Shell appears, fs0: not found |
Hypervisor loaded 64-bit OVMF; FAT partition unreadable | Replace with IA32 OVMF (QEMU: if=pflash; VirtualBox: VBoxInternal/Devices/pcbios/0/Config/BiosRom; VMware: change guest OS to 32-bit Linux) |
| GRUB2 loads, kernel panic: kernel requires features not present on this CPU | QEMU CPU model is too old or too new (e.g. qemu64 exposes x86_64 flags) |
Use -cpu pentium or -cpu pentium2
|
| Boot stalls after Waiting for device /dev/disk/by-label/rootfs... | VM disk not seen as virtio; kernel waits for the Intel Quark eMMC driver |
Re-attach the .wic with if=virtio (QEMU) or change controller to virtio-scsi-pci and add virtio_scsi to the initramfs |
eth0 never appears |
NIC type is vmxnet3 or virtio-net with no driver |
Use e1000; check dmesg | grep -i eth
|
| Network up, but DNS does not resolve | systemd-resolved disabled or /etc/resolv.conf empty |
Enable systemd-resolved and symlink /etc/resolv.conf to /run/systemd/resolve/stub-resolv.conf
|
VM appears to boot but journalctl -xb shows "Watchdog detected" |
Real watchdog timer not emulated; kernel fires it on slow QEMU start-up | Add nowatchdog to the kernel command line in grub.cfg
|
| Snapshot of VM refuses to resume |
wic image mounted as independent-nonpersistent but rootfs journaled |
Switch to persistent mode in VMware, or in QEMU use drive.file.locking=off
|
| VMware 15 BSODs on Windows 10 22H2 when attaching the raw disk | Storage controller driver signature mismatch | Update to Workstation 15.5.7+ or move to Workstation 17; convert .wic to .vmdk with qemu-img convert -O vmdk and use that instead of a raw-disk descriptor |
| VirtualBox "VERR_NEM_NOT_AVAILABLE" on Apple Silicon (M1/M2) | VirtualBox 6.x is x86-only; the IOT2000 image is x86 | Use VMware Fusion 13 (which x86-emulates) or Parallels 18 with the Other Linux 32-bit profile; ARM-native IOT2050 image is the only path on Apple Silicon with full performance |
12. Migrating from IOT2040 to IOT2050 — When to Stop Virtualizing
Siemens has replaced the IOT2040 with the SIMATIC IOT2050, which uses an ARM-based TI AM6528 SoC (ARMv8, 64-bit) and a 64-bit UEFI. The IOT2050 ships with a Yocto image based on the github.com/siemens/iot2050 meta-layer and a .wic file with a GPT partition table. Virtualizing the IOT2050 is significantly easier on QEMU with qemu-system-aarch64 and a 64-bit OVMF blob — there is no 32-bit UEFI constraint. If your project is greenfield and does not require the Arduino Uno R3 shield, plan the migration now: the IOT2050 has more RAM, more CPU cores, and a real-time capable PRU-ICSS for industrial protocols, none of which you can faithfully emulate in a 32-bit Quark VM.
For teams that must keep the IOT2040 in production, treat the VM as a fast iteration environment for the application layer — protocol stacks, web dashboards, MQTT brokers, and CI tests. Reserve final integration testing for physical hardware: at least one IOT2040 per developer plus one bench unit for regression.
Can the IOT2040 image be run as-is in VMware Workstation 15 on Windows?
Yes, but you must configure the guest OS as a 32-bit Linux so Workstation loads its IA32 firmware shim, attach the .wic as a raw disk through a VMDK descriptor, and select the e1000 virtual NIC. The default 64-bit UEFI will drop you into the UEFI Interactive Shell because the IOT2040 ships a 32-bit BOOTIA32.EFI GRUB2 binary.
Why does VirtualBox show a UEFI Interactive Shell and not boot the IOT2040 image?
VirtualBox 6.1/7.0 distribute an X64 OVMF firmware only. The IOT2040's 32-bit EFI binary cannot be loaded by 64-bit firmware, so no GRUB is executed. Replace the firmware with a 32-bit OVMF via the VBoxInternal/Devices/pcbios/0/Config/BiosRom extra-data entry, or fall back to a QEMU-based VM that lets you point at an explicit IA32 firmware file.
Is the .wic file a raw disk image, a compressed archive, or a VMDK?
It is a raw, uncompressed disk image produced by the Yocto wic tool. It contains an MBR partition table with a small FAT EFI System Partition and an ext4 rootfs partition. You can inspect it with fdisk -l image-iot2040.wic on Linux or convert it to VMDK with qemu-img convert -O vmdk image-iot2040.wic disk.vmdk.
Do the Arduino shield, RS485, and mini-PCIe ports work in the VM?
No. None of the Intel Quark on-board peripherals (Arduino shield GPIOs/SPI/I2C/ADC, RS485 transceiver, mini-PCIe signals) are emulated by QEMU, VMware, or VirtualBox. The virtual machine is a network and user-space test bench only. Use a physical IOT2040 for hardware integration testing or migrate to the IOT2050, which has a different shield ecosystem.
Which virtual NIC should I attach to the IOT2040 VM for best results?
Use the Intel e1000 (82540EM) in VMware and VirtualBox — the driver is built into every IOT2000 kernel. In QEMU, prefer virtio-net-pci for performance; the virtio_net module is included in the IOT2040 default kernel. Avoid vmxnet3 (no driver) and virtio-net on VirtualBox unless you have explicitly loaded the module on the loop-mounted rootfs.