Merge branch 'contrib/github_pr_15974' into 'master'

fix(tools/idf-qemu): Append qemu_extra_args after monitor -serial not before (GitHub PR)

Closes IDFGH-15315

See merge request espressif/esp-idf!39257
This commit is contained in:
Ivan Grokhotkov
2025-07-14 12:03:22 +02:00

View File

@@ -1,10 +1,11 @@
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import atexit import atexit
import binascii import binascii
import fnmatch import fnmatch
import json import json
import os import os
import shlex
import shutil import shutil
import socket import socket
import subprocess import subprocess
@@ -18,7 +19,10 @@ from typing import List
from click.core import Context from click.core import Context
try: try:
from idf_py_actions.tools import PropertyDict, ensure_build_directory, red_print, yellow_print from idf_py_actions.tools import PropertyDict
from idf_py_actions.tools import ensure_build_directory
from idf_py_actions.tools import red_print
from idf_py_actions.tools import yellow_print
except ImportError: except ImportError:
PropertyDict = Any PropertyDict = Any
@@ -37,6 +41,7 @@ class QemuTarget:
""" """
Target-specific information related to QEMU. Target-specific information related to QEMU.
""" """
target: str # chip name, e.g. esp32, esp32c3 target: str # chip name, e.g. esp32, esp32c3
qemu_prog: str # name of the QEMU binary, e.g. qemu-system-xtensa qemu_prog: str # name of the QEMU binary, e.g. qemu-system-xtensa
install_package: str # name of the tools.json package from which to install the QEMU binary install_package: str # name of the tools.json package from which to install the QEMU binary
@@ -60,10 +65,11 @@ QEMU_TARGETS: Dict[str, QemuTarget] = {
'00000000000000000000000000800000000000000000100000000000000000000000000000000000' '00000000000000000000000000800000000000000000100000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000'), '00000000'
),
'-global driver=esp32.gpio,property=strap_mode,value=0x0f', '-global driver=esp32.gpio,property=strap_mode,value=0x0f',
'nvram.esp32.efuse'), 'nvram.esp32.efuse',
),
'esp32c3': QemuTarget( 'esp32c3': QemuTarget(
'esp32c3', 'esp32c3',
'qemu-system-riscv32', 'qemu-system-riscv32',
@@ -96,10 +102,11 @@ QEMU_TARGETS: Dict[str, QemuTarget] = {
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'000000000000000000000000000000000000000000000000'), '000000000000000000000000000000000000000000000000'
),
'-global driver=esp32c3.gpio,property=strap_mode,value=0x02', '-global driver=esp32c3.gpio,property=strap_mode,value=0x02',
'nvram.esp32c3.efuse'), 'nvram.esp32c3.efuse',
),
'esp32s3': QemuTarget( 'esp32s3': QemuTarget(
'esp32s3', 'esp32s3',
'qemu-system-xtensa', 'qemu-system-xtensa',
@@ -132,9 +139,11 @@ QEMU_TARGETS: Dict[str, QemuTarget] = {
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'000000000000000000000000000000000000000000000000'), '000000000000000000000000000000000000000000000000'
),
'-global driver=esp32s3.gpio,property=strap_mode,value=0x07', '-global driver=esp32s3.gpio,property=strap_mode,value=0x07',
'nvram.esp32c3.efuse'), # Not esp32s3, QEMU-201 'nvram.esp32c3.efuse',
), # Not esp32s3, QEMU-201
} }
@@ -142,6 +151,7 @@ class QemuTaskRunOptions:
""" """
Some options related to QEMU execution, which depend on the presence of other tasks: gdb and monitor. Some options related to QEMU execution, which depend on the presence of other tasks: gdb and monitor.
""" """
def __init__(self) -> None: def __init__(self) -> None:
self.bg_mode = False self.bg_mode = False
self.wait_for_gdb = False self.wait_for_gdb = False
@@ -213,7 +223,16 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
project_desc = json.load(f) project_desc = json.load(f)
return project_desc return project_desc
def qemu(action: str, ctx: Context, args: PropertyDict, qemu_extra_args: str, gdb: bool, graphics: bool, efuse_file: str, flash_file: str) -> None: def qemu(
action: str,
ctx: Context,
args: PropertyDict,
qemu_extra_args: str,
gdb: bool,
graphics: bool,
efuse_file: str,
flash_file: str,
) -> None:
project_desc = _get_project_desc(args, ctx) project_desc = _get_project_desc(args, ctx)
# Determine the target and check if we have the necessary QEMU binary # Determine the target and check if we have the necessary QEMU binary
@@ -223,9 +242,11 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
red_print(f'QEMU is not supported for target {target}') red_print(f'QEMU is not supported for target {target}')
raise SystemExit(1) raise SystemExit(1)
if not shutil.which(qemu_target_info.qemu_prog): if not shutil.which(qemu_target_info.qemu_prog):
red_print(f'{qemu_target_info.qemu_prog} is not installed. Please install it using ' red_print(
f'{qemu_target_info.qemu_prog} is not installed. Please install it using '
f'"python $IDF_PATH/tools/idf_tools.py install {qemu_target_info.install_package}" ' f'"python $IDF_PATH/tools/idf_tools.py install {qemu_target_info.install_package}" '
'or build it from source if the pre-built version is not available for your platform.') 'or build it from source if the pre-built version is not available for your platform.'
)
raise SystemExit(1) raise SystemExit(1)
# Generate flash image and efuse image # Generate flash image and efuse image
@@ -237,14 +258,24 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
open(bin_path, 'rb').close() open(bin_path, 'rb').close()
yellow_print(f'Using provided flash image: {bin_path}') yellow_print(f'Using provided flash image: {bin_path}')
except FileNotFoundError: except FileNotFoundError:
red_print(f'The provided flash image file \"{bin_path}\" could not be found') red_print(f'The provided flash image file "{bin_path}" could not be found')
raise SystemExit(1) raise SystemExit(1)
else: else:
bin_path = os.path.join(args.build_dir, 'qemu_flash.bin') bin_path = os.path.join(args.build_dir, 'qemu_flash.bin')
yellow_print(f'Generating flash image: {bin_path}') yellow_print(f'Generating flash image: {bin_path}')
subprocess.check_call([ 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) 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,
)
if efuse_file: if efuse_file:
efuse_bin_path = efuse_file efuse_bin_path = efuse_file
@@ -264,13 +295,17 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
# When boot mode is specified, the flash image is not required. # When boot mode is specified, the flash image is not required.
if not options.boot_mode: if not options.boot_mode:
qemu_args += [ qemu_args += [
'-drive', f'file={bin_path},if=mtd,format=raw', '-drive',
f'file={bin_path},if=mtd,format=raw',
] ]
qemu_args += [ qemu_args += [
'-drive', f'file={efuse_bin_path},if=none,format=raw,id=efuse', '-drive',
'-global', f'driver={qemu_target_info.efuse_device},property=drive,value=efuse', f'file={efuse_bin_path},if=none,format=raw,id=efuse',
'-global', f'driver=timer.{target}.timg,property=wdt_disable,value=true', '-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: if '-nic' not in qemu_extra_args:
@@ -279,9 +314,6 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
if options.wait_for_gdb or gdb: if options.wait_for_gdb or gdb:
qemu_args += ['-gdb', f'tcp::{QEMU_PORT_GDB}', '-S'] qemu_args += ['-gdb', f'tcp::{QEMU_PORT_GDB}', '-S']
if qemu_extra_args:
qemu_args += qemu_extra_args.split(' ')
if graphics: if graphics:
qemu_args += ['-display', 'sdl'] qemu_args += ['-display', 'sdl']
else: else:
@@ -293,6 +325,12 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
# Launch QEMU! # Launch QEMU!
if not options.bg_mode: if not options.bg_mode:
qemu_args += ['-serial', 'mon:stdio'] qemu_args += ['-serial', 'mon:stdio']
# Adding qemu_extra_args at the end ensures the monitor is the
# primary serial port if another -serial argument is present.
if qemu_extra_args:
qemu_args += shlex.split(qemu_extra_args)
yellow_print('Running qemu (fg): ' + ' '.join(qemu_args)) yellow_print('Running qemu (fg): ' + ' '.join(qemu_args))
subprocess.run(qemu_args) subprocess.run(qemu_args)
else: else:
@@ -301,14 +339,22 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
else: else:
qemu_args += ['-serial', f'tcp::{QEMU_PORT_SERIAL},server,nowait'] qemu_args += ['-serial', f'tcp::{QEMU_PORT_SERIAL},server,nowait']
# Adding qemu_extra_args at the end ensures the monitor is the
# primary serial port if another -serial argument is present.
if qemu_extra_args:
qemu_args += shlex.split(qemu_extra_args)
yellow_print('Running qemu (bg): ' + ' '.join(qemu_args)) yellow_print('Running qemu (bg): ' + ' '.join(qemu_args))
qemu_proc = subprocess.Popen(qemu_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) qemu_proc = subprocess.Popen(
qemu_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE
)
wait_for_socket(QEMU_PORT_SERIAL) wait_for_socket(QEMU_PORT_SERIAL)
def cleanup_qemu() -> None: def cleanup_qemu() -> None:
if qemu_proc: if qemu_proc:
qemu_proc.terminate() qemu_proc.terminate()
qemu_proc.wait() qemu_proc.wait()
atexit.register(cleanup_qemu) atexit.register(cleanup_qemu)
if qemu_proc.poll() is not None: if qemu_proc.poll() is not None:
yellow_print('QEMU exited with error') yellow_print('QEMU exited with error')
@@ -337,9 +383,11 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
}, },
{ {
'names': ['-d', '--gdb'], 'names': ['-d', '--gdb'],
'help': ('Wait for gdb to connect. ' 'help': (
'Wait for gdb to connect. '
'Use this option to run "idf.py qemu --gdb monitor" in one terminal window ' 'Use this option to run "idf.py qemu --gdb monitor" in one terminal window '
'and "idf.py gdb" in another. The program will start running when gdb connects.'), 'and "idf.py gdb" in another. The program will start running when gdb connects.'
),
'is_flag': True, 'is_flag': True,
'default': False, 'default': False,
}, },
@@ -351,20 +399,24 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
}, },
{ {
'names': ['--efuse-file'], 'names': ['--efuse-file'],
'help': ('File used to store efuse values. If not specified, qemu_efuse.bin file ' 'help': (
'in build directory is used.'), 'File used to store efuse values. If not specified, qemu_efuse.bin file '
'in build directory is used.'
),
'is_flag': False, 'is_flag': False,
'default': '', 'default': '',
}, },
{ {
'names': ['--flash-file'], 'names': ['--flash-file'],
'help': ('File used as the qemu flash image. If not specified, qemu_flash.bin file ' 'help': (
'in build directory is used.'), 'File used as the qemu flash image. If not specified, qemu_flash.bin file '
'in build directory is used.'
),
'is_flag': False, 'is_flag': False,
'default': '', 'default': '',
},
],
} }
] },
}
}
} }
return qemu_actions return qemu_actions