This page provides some ESP-specific instructions for running QEMU. Refer to the official documentation for general QEMU usage questions: https://www.qemu.org/documentation/.
Beforehand, please check QEMU documentation regarding build prerequisites. If you are building QEMU on a Linux host, you can refer to this page.
In addition to QEMU's prerequisites, make sure that libgcrypt
is installed on your system (libgcrypt-devel
on Ubuntu, libgcrypt
on Arch, libgcrypt
on macOS Homebrew).
To generate the ninja
build files, we need to configure the project first, the following command can be used for that:
./configure --target-list=xtensa-softmmu \
--enable-gcrypt \
--enable-slirp \
--enable-debug --enable-sanitizers \
--enable-sdl \
--disable-strip --disable-user \
--disable-capstone --disable-vnc \
--disable-gtk
To reduce the first compilation time, feel free to add more --disable
options. The complete list of options that can be enabled or disabled can be obtained with ./configure --help
command.
If you need a graphical interface for the virtual machine, make sure to provide --enable-sdl
or --enable-gtk
or both.
After configuring the project successfully, ninja
can be used to build it:
ninja -C build
Compilation can take a few minutes depending on the components that were enabled or disabled previously. Upon completion, build/qemu-system-xtensa
should be created.
QEMU for the ESP32 target is ready, it already includes the first stage bootloader, located on the ROM on the real chip, which is mainly responsible for initializing the peripherals, like the UART and, more importantly, the SPI Flash. The latter must contain the second stage bootloader and the program to run.
Thus, in this section, we are going to create a flash image that combines the (second stage) bootloader, the partition table and the application to run. This can be done using esptool.py merge_bin
command, supported in esptool.py
3.1 or later. Let's suppose that the ESP-IDf project has just been compiled successfully, the following commands will create that flash image:
cd build
esptool.py --chip esp32 merge_bin --fill-flash-size 4MB -o flash_image.bin @flash_args
Here, flash_args
is a file generated by ESP-IDF build system in the build directory, it contains the list of names of binary files and corresponding flash addresses. merge_bin
command takes this list and creates the binary image of the whole flash. --fill-flash-size 4MB
argument specifies the total flash size.
ESP32 target in QEMU supports flash of size 2, 4, 8 and 16MB, creating an image with any other size will result in an error.
Notes
-
For "Secure Boot" feature in ESP-IDF, we recommend separate command to flash bootloader and hence
flash_args
file do not have corresponding entry. However, you may modifyflash_args
file to add entry forbootloader.bin
as per below:0x1000 bootloader/bootloader.bin
-
It is also possible to use
esptool.py
to "flash" the application into QEMU, but QEMU needs to be started with the right strapping mode. See Bootstrapping Mode section below for more info.
If you don't need to debug the guest application, you can execute QEMU without attaching GDB to it:
build/qemu-system-xtensa -nographic \
-machine esp32 \
-drive file=flash_image.bin,if=mtd,format=raw
Where flash_image.bin
is the SPI flash image generated previously.
If you need to debug the guest application, you can execute QEMU with -s -S
options. This tells QEMU not to start the CPU after initializing the virtual machine. It will wait for a connection from a GDB client:
build/qemu-system-xtensa -nographic -s -S \
-machine esp32 \
-drive file=flash_image.bin,if=mtd,format=raw
Where flash_image.bin
is the SPI flash image generated previously.
Then, to connect the GDB client, use the following command:
xtensa-esp32-elf-gdb build/app-name.elf \
-ex "target remote :1234" \
-ex "monitor system_reset" \
-ex "tb app_main" -ex "c"
The last line sets a breakpoint in app_main
function of the guest application and starts the virtual CPU with c
. If you need to put a breakpoint in any other functions or if you don't need to start the CPU directly, please adapt this last line.
If you are using esp2021r1 or an earlier toolchain release, GDB may report the following error when connecting to QEMU:
Remote 'g' packet reply is too long (expected 420 bytes, got 628 bytes):
In that case, you need to set the following environment variable beforehand:
export QEMU_XTENSA_CORE_REGS_ONLY=1
When this environment variable is set, QEMU will only send the values of non-privileged registers to GDB.
If you are using later releases of Xtensa toolchain, i.e. esp-2021r2 and later, GDB will work out-of-the-box, without the need to set this environment variable.
Starting from IDF 4.1, the following hardware crypto features are enabled by default: AES, SHA, RSA.
All of them are implemented in QEMU for ESP32 target. However, please note that the SHA emulation currently doesn't support concurrent operations with different SHA types.
Support for Opencores Ethernet MAC in ESP-IDF is added in https://github.com/espressif/esp-idf/commit/31dac92e5f0daac98190fd603df213a0a25a3807.
- When running
protocols
examples, enableCONFIG_EXAMPLE_CONNECT_ETHERNET
andCONFIG_EXAMPLE_USE_OPENETH
. - When running a custom app, enable
CONFIG_ETH_USE_OPENETH
and initialize the Ethernet driver as it is done in examples/common_components/protocol_examples_common/connect.c (look foresp_eth_mac_new_openeth
).
When starting QEMU, use open_eth
network device.
For example, to start networking in user mode (TCP/UDP only, emulated device is behind NAT), add the following option to the QEMU command line:
-nic user,model=open_eth
Some ESP projects (specifically running TCP listeners) might need port forwarding to be setup,
-nic user,model=open_eth,id=lo0,hostfwd=tcp:127.0.0.1:PORT_HOST-:PORT_GUEST
(e.g. asio-echo-server sets up a server on 2222 by default, so hostfwd=tcp:127.0.0.1:2222-:2222
enables to nc localhost 2222
from host machine)
To specify the desired strapping mode, it is necessary to add the following argument when running QEMU:
-global driver=esp32.gpio,property=strap_mode,value=0x0f
This sets the value of GPIO_STRAP
register.
- Use
0x12
for flash boot mode (default) - Use
0x0f
for UART-only download mode (since the SDIO part is not implemented)
Add extra arguments to the command line:
-drive file=qemu_efuse.bin,if=none,format=raw,id=efuse
-global driver=nvram.esp32.efuse,property=drive,value=efuse
The first argument creates a block device backed by qemu_efuse.bin
file, with identifier efuse
. The second line configures nvram.esp32.efuse
device to use this block device for storage.
The file must be created before starting QEMU:
dd if=/dev/zero bs=1 count=124 of=/tmp/qemu_efuse.bin
124 bytes is the total size of ESP32 eFuse blocks.
Note
Specifying eFuse storage is mandatory to test out any platform security features like "Secure Boot" or "Flash Encryption".
For the application to detect the emulated chip as ESP32 ECO3, the following virtual efuses must be set:
- CHIP_VER_REV1
- CHIP_VER_REV2
Here is the corresponding efuse file (in hexadecimal, produced using xxd -p
):
000000000000000000000000008000000000000000001000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
00000000
To convert this (efuse.hex
) back to binary, run xxd -r -p efuse.hex qemu_efuse.bin
.
Alternatively, these bits can be set using espefuse:
espefuse.py --port=socket://localhost:5555 burn_efuse CHIP_VER_REV1
espefuse.py --port=socket://localhost:5555 burn_efuse CHIP_VER_REV2
By default, Timer Group watchdog timers are emulated, and TG0 WDT is enabled at reset. It is sometimes useful to disable these watchdog timers. This can be done by adding the following to the command line:
-global driver=timer.esp32.timg,property=wdt_disable,value=true
This disables the emulation of TG watchdog timers. Even if the application configures them, they will not fire.
The RTC watchdog timer is not emulated yet, so it doesn't need to be disabled.
-
Start QEMU:
build/qemu-system-xtensa -nographic \ -machine esp32 \ -drive file=flash_image.bin,if=mtd,format=raw \ -global driver=esp32.gpio,property=strap_mode,value=0x0f \ -drive file=qemu_efuse.bin,if=none,format=raw,id=efuse \ -global driver=nvram.esp32.efuse,property=drive,value=efuse \ -serial tcp::5555,server,nowait
The final line redirects the emulated UART to TCP port 5555 (QEMU acts as a server).
Type q and press Enter at any time to quit.
-
Run esptool.py:
esptool.py -p socket://localhost:5555 flash_id
Flashing with
idf.py
also works:export ESPPORT=socket://localhost:5555 idf.py flash
-
Or, run espefuse.py:
espefuse.py --port socket://localhost:5555 --do-not-confirm burn_custom_mac 00:11:22:33:44:55
Note: esptool
can not reset the emulated chip using the RTS signal, because the state of RTS is not transmitted over TCP to QEMU. To reset the emulated chip, run system_reset
command in QEMU console (started at step 1).
If -kernel
and -bios
arguments are not given, ESP32 (rev. 3) ROM code will be loaded. This ROM code binary is included in the repository. To specify the ROM code ELF file to load, pass the filename with a -bios <filename>
argument.
- In the IDF application, enable
CONFIG_SECURE_FLASH_ENC_ENABLED
throughmenuconfig
, and build it - Build the flash image as per the instructions from the Compiling the ESP-IDF program to emulate section.
- Create
qemu_efuse.bin
as highlighted in the Specifying eFuse storage section. - Execute
qemu-system-xtensa
using the following command:build/qemu-system-xtensa -nographic -machine esp32 \ -drive file=/path/to/qemu_efuse.bin,if=none,format=raw,id=efuse \ -global driver=nvram.esp32.efuse,property=drive,value=efuse \ -drive file=/path/to/flash_image.bin,if=mtd,format=raw \ -global driver=timer.esp32.timg,property=wdt_disable,value=true
QEMU "memory size" option can be used to enable PSRAM emulation. By default, no PSRAM is added to the machine. You can add 2MB or 4MB PSRAM using -m 2M
or -m 4M
command line options, respectively.
Note that PSRAM MMU is not emulated yet, so things like bank switching (himem
in IDF) do not work.
QEMU emulates SD/MMC host controller used in ESP32. To add an SD card to the system, create an image and pass it to QEMU.
-
Create a raw image file, for example, 64 MB:
$ dd if=/dev/zero bs=$((1024*1024)) count=64 of=sd_image.bin
-
Add the following argument when running QEMU:
-drive file=sd_image.bin,if=sd,format=raw
If you need to create a large SD card image, it is recommended to use sparse cqow2
images instead of raw ones. Consult QEMU manual about qemu-img
tool for details.
Only one SD card is supported at a time. You can use either slot 0 or slot 1 of the SD/MMC controller in the application code.
The ESP32 QEMU implementation implements a virtual RGB panel, absent on the real hardware, that can be used to show graphical interface. It is associated to a virtual frame buffer that can be used to populate the pixels to show. It is also possible to use the target internal RAM as a frame buffer.
To enable the graphical interface, while keeping the serial output in the console, use the following command line:
build/qemu-system-xtensa \
-machine esp32 \
-drive file=flash_image.bin,if=mtd,format=raw
-display sdl \
-serial stdio
If gtk
backend was enabled when compiling QEMU, it is possible to replace -display sdl
with -display gtk