Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for efuse in qemu #291

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions pytest-embedded-qemu/pytest_embedded_qemu/qemu.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import asyncio
import binascii
import logging
import os
import shlex
import socket
Expand All @@ -12,6 +14,43 @@
if t.TYPE_CHECKING:
from .app import QemuApp

QEMU_DEFAULT_EFUSE = {
'esp32': binascii.unhexlify(
'00000000000000000000000000800000000000000000100000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000'
),
'esp32c3': binascii.unhexlify(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also support ESP32-S3 now, please add it as well.

'00000000000000000000000000000000000000000000000000000000000000000000000000000c00'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
'000000000000000000000000000000000000000000000000'
),
}


class Qemu(DuplicateStdoutPopen):
"""
Expand All @@ -37,6 +76,7 @@ def __init__(
qemu_prog_path: t.Optional[str] = None,
qemu_cli_args: t.Optional[str] = None,
qemu_extra_args: t.Optional[str] = None,
qemu_efuse_path: t.Optional[str] = None,
app: t.Optional['QemuApp'] = None,
**kwargs,
):
Expand All @@ -55,11 +95,28 @@ def __init__(

qemu_prog_path = qemu_prog_path or self.qemu_prog_name

self.current_qemu_executable_path = qemu_prog_path
self.image_path = image_path
self.efuse_path = qemu_efuse_path

if qemu_cli_args:
qemu_cli_args = qemu_cli_args.strip('"').strip("'")
qemu_cli_args = shlex.split(qemu_cli_args or self.qemu_default_args)
qemu_extra_args = shlex.split(qemu_extra_args or '')

if self.efuse_path:
logging.debug('The eFuse file will be saved to: %s', self.efuse_path)
with open(self.efuse_path, 'wb') as f:
f.write(QEMU_DEFAULT_EFUSE[self.app.target])
qemu_extra_args += [
'-global',
f'driver={self.app.target}.gpio,property=strap_mode,value=0x08',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strap_mode is dependent on the chip target, please search for strap_mode in IDF's qemu_ext.py.

'-drive',
f'file={self.efuse_path},if=none,format=raw,id=efuse',
'-global',
f'driver=nvram.{self.app.target}.efuse,property=drive,value=efuse',
]

self.qmp_addr = None
self.qmp_port = None

Expand Down Expand Up @@ -87,6 +144,47 @@ def __init__(
**kwargs,
)

def execute_efuse_command(self, command: str):
import espefuse
import pexpect

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', 0))
_, available_port = s.getsockname()

run_qemu_command = [
'-nographic',
'-machine',
self.app.target,
'-drive',
f'file={self.image_path},if=mtd,format=raw',
'-global',
f'driver={self.app.target}.gpio,property=strap_mode,value=0x0f',
'-drive',
f'file={self.efuse_path},if=none,format=raw,id=efuse',
'-global',
f'driver=nvram.{self.app.target}.efuse,property=drive,value=efuse',
'-serial',
f'tcp::{available_port},server,nowait',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When launching QEMU and expecting that it should communicate with another application, it is better to use -daemonize flag — as recommended by QEMU documentation. Otherwise there may be a race condition between QEMU getting ready to accept TCP connections and the launch of the other application. It seems child.expect('qemu') aims to work around that, but I am not sure this is reliable enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, now that I'm thinking of this... don't you end up with two QEMU processes running at the same time, and accessing the same eFuse file?.. One QEMU process is already launched when dut is created, another one is launched here.

]
try:
child = pexpect.spawn(self.current_qemu_executable_path, run_qemu_command)
res = shlex.split(command)
child.expect('qemu')

res = [r for r in res if r != '--do-not-confirm']
espefuse.main([
'--port',
f'socket://localhost:{available_port}',
'--before',
'no_reset',
'--do-not-confirm',
*res,
])
self._hard_reset()
finally:
child.terminate()

@property
def qemu_prog_name(self):
if self.app:
Expand Down
44 changes: 44 additions & 0 deletions pytest-embedded-qemu/tests/test_qemu.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,50 @@
)


@qemu_bin_required
def test_pexpect_write_efuse(testdir):
testdir.makepyfile("""
import pexpect
import pytest

def test_pexpect_by_qemu(dut):
dut.qemu.execute_efuse_command('burn_custom_mac 00:11:22:33:44:55')
dut.expect('')

with open('/tmp/test.test', 'rb') as f:
content = f.read()

expected_output = [
'00000000:00000000000000000000000000800000',
'00000010:00000000000010000000000000000000',
'00000020:00000000000000000000000000000000',
'00000030:00000000000000000000000000000000',
'00000040:00000000000000000000000000000000',
'00000050:000000000000000000000000b8001122',
'00000060:33445500000000000000000000000000',
'00000070:000000010000000000000000'
]

lines = []
for i in range(0, len(content), 16):
line = content[i:i+16]
hex_values = ''.join(f'{byte:02x}' for byte in line)
lines.append(f'{i:08x}:{hex_values}')
assert lines == expected_output

""")

result = testdir.runpytest(
'-s',
'--embedded-services', 'idf,qemu',
'--app-path', os.path.join(testdir.tmpdir, 'hello_world_esp32'),
'--qemu-cli-args="-machine esp32 -nographic"',
'--qemu-efuse-path', '/tmp/test.test'
)

result.assert_outcomes(passed=1)


@qemu_bin_required
def test_pexpect_by_qemu_xtensa(testdir):
testdir.makepyfile("""
Expand Down
6 changes: 6 additions & 0 deletions pytest-embedded/pytest_embedded/dut_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def _fixture_classes_and_options_fn(
qemu_prog_path,
qemu_cli_args,
qemu_extra_args,
qemu_efuse_path,
wokwi_cli_path,
wokwi_timeout,
wokwi_scenario,
Expand Down Expand Up @@ -181,6 +182,7 @@ def _fixture_classes_and_options_fn(
'encrypt': encrypt,
'keyfile': keyfile,
'qemu_prog_path': qemu_prog_path,
'qemu_efuse_path': qemu_efuse_path,
})
else:
from pytest_embedded_idf import IdfApp
Expand Down Expand Up @@ -290,6 +292,7 @@ def _fixture_classes_and_options_fn(
'qemu_prog_path': qemu_prog_path,
'qemu_cli_args': qemu_cli_args,
'qemu_extra_args': qemu_extra_args,
'qemu_efuse_path': qemu_efuse_path,
'app': None,
'meta': _meta,
'dut_index': dut_index,
Expand Down Expand Up @@ -607,6 +610,7 @@ def create(
qemu_prog_path: t.Optional[str] = None,
qemu_cli_args: t.Optional[str] = None,
qemu_extra_args: t.Optional[str] = None,
qemu_efuse_path: t.Optional[str] = None,
wokwi_cli_path: t.Optional[str] = None,
wokwi_timeout: t.Optional[int] = 0,
wokwi_scenario: t.Optional[str] = None,
Expand Down Expand Up @@ -655,6 +659,7 @@ def create(
qemu_prog_path: QEMU program path.
qemu_cli_args: QEMU CLI arguments.
qemu_extra_args: Additional QEMU arguments.
qemu_efuse_path: Efuse binary path.
wokwi_cli_path: Wokwi CLI path.
wokwi_timeout: Wokwi timeout.
wokwi_scenario: Wokwi scenario path.
Expand Down Expand Up @@ -719,6 +724,7 @@ def create(
'qemu_prog_path': qemu_prog_path,
'qemu_cli_args': qemu_cli_args,
'qemu_extra_args': qemu_extra_args,
'qemu_efuse_path': qemu_efuse_path,
'wokwi_cli_path': wokwi_cli_path,
'wokwi_timeout': wokwi_timeout,
'wokwi_scenario': wokwi_scenario,
Expand Down
12 changes: 12 additions & 0 deletions pytest-embedded/pytest_embedded/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ def pytest_addoption(parser):
'--qemu-extra-args',
help='QEMU cli extra arguments, will append to the argument list. (Default: None)',
)
qemu_group.addoption(
'--qemu-efuse-path',
help='This option makes it possible to use efuse in QEMU when it is set up.',
)
qemu_group.addoption(
'--skip-regenerate-image',
help='y/yes/true for True and n/no/false for False. '
Expand Down Expand Up @@ -932,6 +936,13 @@ def qemu_extra_args(request: FixtureRequest) -> t.Optional[str]:
return _request_param_or_config_option_or_default(request, 'qemu_extra_args', None)


@pytest.fixture
@multi_dut_argument
def qemu_efuse_path(request: FixtureRequest) -> t.Optional[str]:
"""Enable parametrization for the same cli option"""
return _request_param_or_config_option_or_default(request, 'qemu_efuse_path', None)


@pytest.fixture
@multi_dut_argument
def skip_regenerate_image(request: FixtureRequest) -> t.Optional[str]:
Expand Down Expand Up @@ -1039,6 +1050,7 @@ def parametrize_fixtures(
qemu_prog_path,
qemu_cli_args,
qemu_extra_args,
qemu_efuse_path,
wokwi_cli_path,
wokwi_timeout,
wokwi_scenario,
Expand Down
Loading