diff --git a/docs/en/api-guides/tools/qemu.rst b/docs/en/api-guides/tools/qemu.rst index 505a5cb03008..697f45ca7cd4 100644 --- a/docs/en/api-guides/tools/qemu.rst +++ b/docs/en/api-guides/tools/qemu.rst @@ -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 `_ 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` 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 diff --git a/docs/zh_CN/api-guides/tools/qemu.rst b/docs/zh_CN/api-guides/tools/qemu.rst index a5151bcda6e3..14f962dbcfef 100644 --- a/docs/zh_CN/api-guides/tools/qemu.rst +++ b/docs/zh_CN/api-guides/tools/qemu.rst @@ -111,3 +111,21 @@ QEMU 支持虚拟帧缓冲设备。帧缓冲设备在真实的 {IDF_TARGET_NAME} 当启用 ``--graphics`` 选项时,QEMU 会打开一个额外的窗口,显示帧缓冲内容。 要在应用程序中使用虚拟帧缓冲设备,可以将 `espressif/esp_lcd_qemu_rgb `_ 组件添加到项目中。此组件为虚拟帧缓冲设备提供了一个与 esp_lcd 兼容的驱动程序。 + +eFuse 仿真 +~~~~~~~~~~~ + +QEMU 支持 eFuse 的仿真,可用来测试安全启动和 flash 加密等与安全相关的功能,而无需在真实硬件上执行不可逆操作。 + +使用 :doc:`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 diff --git a/tools/idf_py_actions/qemu_ext.py b/tools/idf_py_actions/qemu_ext.py index 6bd38426237c..685e9c8363ed 100644 --- a/tools/idf_py_actions/qemu_ext.py +++ b/tools/idf_py_actions/qemu_ext.py @@ -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 @@ class 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 @@ class 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 @@ class 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 @@ def __init__(self) -> None: 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 have_task(name: str) -> bool: 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 _get_project_desc(args: PropertyDict, ctx: Context) -> Any: 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 qemu(action: str, ctx: Context, args: PropertyDict, qemu_extra_args: str, gd # 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 qemu(action: str, ctx: Context, args: PropertyDict, qemu_extra_args: str, gd 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 cleanup_qemu() -> None: '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': '', } ] } diff --git a/tools/test_idf_py/test_idf_qemu.py b/tools/test_idf_py/test_idf_qemu.py index 3188d1b6c4d2..de277753c2b7 100755 --- a/tools/test_idf_py/test_idf_qemu.py +++ b/tools/test_idf_py/test_idf_qemu.py @@ -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 @@ def test_idf_qemu(self) -> None: 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__':