feat(idf.py): allow running idf.py efuse-* commands with QEMU

This commit is contained in:
Ivan Grokhotkov 2024-03-22 10:27:27 +01:00
parent 75eb7fe379
commit 7b228ce8ab
No known key found for this signature in database
GPG Key ID: 1E050E141B280628
4 changed files with 101 additions and 21 deletions

View File

@ -111,3 +111,21 @@ To launch QEMU with a virtual framebuffer device enabled, use the following comm
When the ``--graphics`` option is used, QEMU opens an additional window where the framebuffer contents are displayed.
To use the virtual framebuffer device in your application, you can add the `espressif/esp_lcd_qemu_rgb <https://components.espressif.com/components/espressif/esp_lcd_qemu_rgb>`_ component to your project. This component provides an esp_lcd compatible driver for the virtual framebuffer device.
Efuse Emulation
~~~~~~~~~~~~~~~
QEMU supports emulation of eFuses. This can be a convenient way to test security-related features, such as secure boot and flash encryption, without having to perform irreversible operations on real hardware.
You can use :doc:`idf.py<idf-py>` eFuse-related commands to program eFuses. When you run any of these commands together with ``qemu`` command, the eFuses are programmed in QEMU, and the ``qemu_efuse.bin`` file is updated. For example,
.. code-block:: console
idf.py qemu efuse-burn FLASH_CRYPT_CNT 1
By default, the values of eFuses are read from and written to the ``qemu_efuse.bin`` file in the build directory. You can specify a different file using the ``--efuse-file`` option. For example,
.. code-block:: console
idf.py qemu --efuse-file my_efuse.bin efuse-burn FLASH_CRYPT_CNT 1
idf.py qemu --efuse-file my_efuse.bin monitor

View File

@ -111,3 +111,21 @@ QEMU 支持虚拟帧缓冲设备。帧缓冲设备在真实的 {IDF_TARGET_NAME}
当启用 ``--graphics`` 选项时QEMU 会打开一个额外的窗口,显示帧缓冲内容。
要在应用程序中使用虚拟帧缓冲设备,可以将 `espressif/esp_lcd_qemu_rgb <https://components.espressif.com/components/espressif/esp_lcd_qemu_rgb>`_ 组件添加到项目中。此组件为虚拟帧缓冲设备提供了一个与 esp_lcd 兼容的驱动程序。
eFuse 仿真
~~~~~~~~~~~
QEMU 支持 eFuse 的仿真,可用来测试安全启动和 flash 加密等与安全相关的功能,而无需在真实硬件上执行不可逆操作。
使用 :doc:`idf.py<idf-py>` eFuse 相关命令来编程 eFuse。当这些命令与 ``qemu`` 命令一起运行时eFuse 会在 QEMU 中编程,并且 ``qemu_efuse.bin`` 文件会更新。例如,
.. code-block:: console
idf.py qemu efuse-burn FLASH_CRYPT_CNT 1
默认情况下eFuse 的值从编译文件夹里的 ``qemu_efuse.bin`` 文件中读取和写入。也可以使用 ``--efuse-file`` 选项指定不同的文件。例如,
.. code-block:: console
idf.py qemu --efuse-file my_efuse.bin efuse-burn FLASH_CRYPT_CNT 1
idf.py qemu --efuse-file my_efuse.bin monitor

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
import atexit
import binascii
import fnmatch
import json
import os
import shutil
@ -41,6 +42,8 @@ class QemuTarget:
install_package: str # name of the tools.json package from which to install the QEMU binary
qemu_args: str # chip-specific arguments to pass to QEMU
default_efuse: bytes # default efuse values for the target
boot_mode_arg: str = '' # additional arguments to pass to QEMU when booting in download mode
efuse_device: str = '' # efuse device name, if different from the target nvram.{target}.efuse
# To generate the default eFuse values, follow the instructions in
@ -57,7 +60,9 @@ QEMU_TARGETS: Dict[str, QemuTarget] = {
'00000000000000000000000000800000000000000000100000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000')),
'00000000'),
'-global driver=esp32.gpio,property=strap_mode,value=0x0f',
'nvram.esp32.efuse'),
'esp32c3': QemuTarget(
'esp32c3',
@ -91,7 +96,10 @@ QEMU_TARGETS: Dict[str, QemuTarget] = {
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'000000000000000000000000000000000000000000000000')),
'000000000000000000000000000000000000000000000000'),
'-global driver=esp32c3.gpio,property=strap_mode,value=0x02',
'nvram.esp32c3.efuse'),
'esp32s3': QemuTarget(
'esp32s3',
'qemu-system-xtensa',
@ -124,7 +132,9 @@ QEMU_TARGETS: Dict[str, QemuTarget] = {
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'000000000000000000000000000000000000000000000000')),
'000000000000000000000000000000000000000000000000'),
'-global driver=esp32s3.gpio,property=strap_mode,value=0x07',
'nvram.esp32c3.efuse'), # Not esp32s3, QEMU-201
}
@ -136,6 +146,7 @@ class QemuTaskRunOptions:
self.bg_mode = False
self.wait_for_gdb = False
self.wait_for_monitor = False
self.boot_mode = False
def wait_for_socket(port: int, timeout_sec: float = 10.0) -> None:
@ -165,11 +176,12 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
def global_callback(ctx: Context, global_args: Dict, tasks: List) -> None:
# This callback lets us customize QEMU launch arguments depending on the presence of other tasks.
def have_task(name: str) -> bool:
return any(task.name == name for task in tasks)
return any(fnmatch.fnmatch(task.name, name) for task in tasks)
have_qemu = have_task('qemu')
have_gdb = have_task('gdb')
have_monitor = have_task('monitor')
have_efuse = have_task('efuse-*')
if have_qemu:
if have_gdb and have_monitor:
@ -183,6 +195,15 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
options.bg_mode = True
yellow_print(f'Running qemu on {PYSERIAL_PORT}')
global_args['port'] = PYSERIAL_PORT
if have_efuse:
options.bg_mode = True
options.boot_mode = True
yellow_print(f'Running qemu on {PYSERIAL_PORT}')
global_args['port'] = PYSERIAL_PORT
for task in tasks:
if fnmatch.fnmatch(task.name, 'efuse-*'):
if 'before' in task.action_args.keys():
task.action_args['before'] = 'no_reset'
def _get_project_desc(args: PropertyDict, ctx: Context) -> Any:
desc_path = os.path.join(args.build_dir, 'project_description.json')
@ -192,7 +213,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
project_desc = json.load(f)
return project_desc
def qemu(action: str, ctx: Context, args: PropertyDict, qemu_extra_args: str, gdb: bool, graphics: bool) -> None:
def qemu(action: str, ctx: Context, args: PropertyDict, qemu_extra_args: str, gdb: bool, graphics: bool, efuse_file: str) -> None:
project_desc = _get_project_desc(args, ctx)
# Determine the target and check if we have the necessary QEMU binary
@ -209,23 +230,31 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
# Generate flash image and efuse image
flash_size = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_ESPTOOLPY_FLASHSIZE')
bin_path = os.path.join(args.build_dir, 'flash_image.bin')
bin_path = os.path.join(args.build_dir, 'qemu_flash.bin')
yellow_print(f'Generating flash image: {bin_path}')
subprocess.check_call([
sys.executable, '-m', 'esptool', f'--chip={target}', 'merge_bin', f'--output={bin_path}',
f'--fill-flash-size={flash_size}', '@flash_args'], cwd=args.build_dir)
efuse_bin_path = os.path.join(args.build_dir, 'qemu_efuse.bin')
yellow_print(f'Generating efuse image: {efuse_bin_path}')
with open(efuse_bin_path, 'wb') as f:
f.write(qemu_target_info.default_efuse)
if efuse_file:
efuse_bin_path = efuse_file
else:
efuse_bin_path = os.path.join(args.build_dir, 'qemu_efuse.bin')
try:
open(efuse_bin_path, 'rb').close()
yellow_print(f'Using existing efuse image: {efuse_bin_path}')
except FileNotFoundError:
yellow_print(f'Generating efuse image: {efuse_bin_path}')
with open(efuse_bin_path, 'wb') as f:
f.write(qemu_target_info.default_efuse)
# Prepare QEMU launch arguments
qemu_args = [qemu_target_info.qemu_prog]
qemu_args += qemu_target_info.qemu_args.split(' ')
qemu_args += [
'-drive', f'file={bin_path},if=mtd,format=raw',
'-drive', f'file={efuse_bin_path},if=none,format=raw,id=efuse', '-global', f'driver=nvram.{target}.efuse,property=drive,value=efuse',
'-drive', f'file={efuse_bin_path},if=none,format=raw,id=efuse',
'-global', f'driver={qemu_target_info.efuse_device},property=drive,value=efuse',
'-global', f'driver=timer.{target}.timg,property=wdt_disable,value=true',
]
if '-nic' not in qemu_extra_args:
@ -242,6 +271,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
else:
qemu_args += ['-nographic']
if options.boot_mode:
qemu_args += qemu_target_info.boot_mode_arg.split(' ')
# Launch QEMU!
if not options.bg_mode:
qemu_args += ['-serial', 'mon:stdio']
@ -300,6 +332,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'help': 'Enable graphical window',
'is_flag': True,
'default': False,
},
{
'names': ['--efuse-file'],
'help': ('File used to store efuse values. If not specified, qemu_efuse.bin file '
'in build directory is used.'),
'is_flag': False,
'default': '',
}
]
}

View File

@ -1,8 +1,7 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import logging
import os
import sys
@ -23,15 +22,21 @@ class IdfPyQemuTest(unittest.TestCase):
logfile_name = os.path.join(os.environ['IDF_PATH'], 'qemu_log.out')
with open(logfile_name, 'w+b') as logfile, \
pexpect.spawn(sys.executable, args=args, logfile=logfile) as child:
child.expect('Executing action: all')
child.expect_exact('Executing action: all')
logging.info('Waiting for the build to finish...')
child.expect('Executing action: qemu', timeout=120)
child.expect('Generating flash image:')
child.expect('Generating efuse image:')
child.expect('Executing action: monitor')
child.expect('Hello world!')
child.expect('Restarting in 0 seconds', timeout=20)
child.expect('Restarting now.')
child.expect_exact('Executing action: qemu', timeout=120)
child.expect_exact('Generating flash image:')
child.expect_exact('Generating efuse image:')
child.expect_exact('Executing action: monitor')
child.expect_exact('Hello world!')
child.expect_exact('Restarting in 0 seconds', timeout=20)
child.expect_exact('Restarting now.')
args = [idf_py, '-C', hello_world_dir, '-B', build_dir, 'qemu', 'efuse-summary', '--format=summary']
with open(logfile_name, 'w+b') as logfile, \
pexpect.spawn(sys.executable, args=args, logfile=logfile) as child:
child.expect_exact('Executing action: efuse-summary')
child.expect_exact('WR_DIS (BLOCK0)')
if __name__ == '__main__':