Skip to content

Commit ca48197

Browse files
horwachxkloel
authored andcommitted
feat: add support for efuse in qemu
1 parent 8849063 commit ca48197

File tree

5 files changed

+219
-7
lines changed

5 files changed

+219
-7
lines changed

pytest-embedded-qemu/pytest_embedded_qemu/qemu.py

Lines changed: 151 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import asyncio
2+
import binascii
23
import logging
34
import os
45
import shlex
56
import socket
67
import typing as t
8+
from dataclasses import dataclass
79

810
from pytest_embedded.log import DuplicateStdoutPopen
911
from qemu.qmp import QMPClient
@@ -14,6 +16,87 @@
1416
from .app import QemuApp
1517

1618

19+
@dataclass
20+
class QemuTarget:
21+
strap_mode: str
22+
default_efuse: bytes
23+
24+
25+
QEMU_TARGETS: dict[str, QemuTarget] = {
26+
'esp32': QemuTarget(
27+
strap_mode='0x0F',
28+
default_efuse=binascii.unhexlify(
29+
'00000000000000000000000000800000000000000000100000000000000000000000000000000000'
30+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
31+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
32+
'00000000'
33+
),
34+
),
35+
'esp32c3': QemuTarget(
36+
strap_mode='0x02',
37+
default_efuse=binascii.unhexlify(
38+
'00000000000000000000000000000000000000000000000000000000000000000000000000000c00'
39+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
40+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
41+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
42+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
43+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
44+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
45+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
46+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
47+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
48+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
49+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
50+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
51+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
52+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
53+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
54+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
55+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
56+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
57+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
58+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
59+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
60+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
61+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
62+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
63+
'000000000000000000000000000000000000000000000000'
64+
),
65+
),
66+
'esp32s3': QemuTarget(
67+
strap_mode='0x07',
68+
default_efuse=binascii.unhexlify(
69+
'00000000000000000000000000000000000000000000000000000000000000000000000000000c00'
70+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
71+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
72+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
73+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
74+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
75+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
76+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
77+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
78+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
79+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
80+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
81+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
82+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
83+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
84+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
85+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
86+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
87+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
88+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
89+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
90+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
91+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
92+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
93+
'00000000000000000000000000000000000000000000000000000000000000000000000000000000'
94+
'000000000000000000000000000000000000000000000000'
95+
),
96+
),
97+
}
98+
99+
17100
class Qemu(DuplicateStdoutPopen):
18101
"""
19102
QEMU class
@@ -26,9 +109,8 @@ class Qemu(DuplicateStdoutPopen):
26109
QEMU_DEFAULT_ARGS = '-nographic -machine esp32'
27110
QEMU_DEFAULT_FMT = '-nographic -machine {}'
28111

29-
QEMU_STRAP_MODE_FMT = '-global driver=esp32.gpio,property=strap_mode,value={}'
112+
QEMU_STRAP_MODE_FMT = 'driver={}.gpio,property=strap_mode,value={}'
30113
QEMU_SERIAL_TCP_FMT = '-serial tcp::{},server,nowait'
31-
32114
QEMU_DEFAULT_QMP_FMT = '-qmp tcp:127.0.0.1:{},server,wait=off'
33115

34116
def __init__(
@@ -37,6 +119,7 @@ def __init__(
37119
qemu_prog_path: str | None = None,
38120
qemu_cli_args: str | None = None,
39121
qemu_extra_args: str | None = None,
122+
qemu_efuse_path: str | None = None,
40123
app: t.Optional['QemuApp'] = None,
41124
**kwargs,
42125
):
@@ -53,13 +136,28 @@ def __init__(
53136
if not os.path.exists(image_path):
54137
raise ValueError(f"QEMU image path doesn't exist: {image_path}")
55138

56-
qemu_prog_path = qemu_prog_path or self.qemu_prog_name
139+
self.qemu_prog_path = qemu_prog_path or self.qemu_prog_name
140+
self.image_path = image_path
141+
self.efuse_path = qemu_efuse_path
57142

58143
if qemu_cli_args:
59144
qemu_cli_args = qemu_cli_args.strip('"').strip("'")
60145
qemu_cli_args = shlex.split(qemu_cli_args or self.qemu_default_args)
61146
qemu_extra_args = shlex.split(qemu_extra_args or '')
62147

148+
if self.efuse_path:
149+
logging.debug('The eFuse file will be saved to: %s', self.efuse_path)
150+
with open(self.efuse_path, 'wb') as f:
151+
f.write(QEMU_TARGETS[self.app.target].default_efuse)
152+
qemu_extra_args += [
153+
'-global',
154+
self.QEMU_STRAP_MODE_FMT.format(self.app.target, QEMU_TARGETS[self.app.target].strap_mode),
155+
'-drive',
156+
f'file={self.efuse_path},if=none,format=raw,id=efuse',
157+
'-global',
158+
f'driver=nvram.{self.app.target}.efuse,property=drive,value=efuse',
159+
]
160+
63161
self.qmp_addr = None
64162
self.qmp_port = None
65163

@@ -83,10 +181,59 @@ def __init__(
83181
qemu_cli_args += shlex.split(self.QEMU_DEFAULT_QMP_FMT.format(self.qmp_port))
84182

85183
super().__init__(
86-
cmd=[qemu_prog_path, *qemu_cli_args, *qemu_extra_args, '-drive', f'file={image_path},if=mtd,format=raw'],
184+
cmd=[
185+
self.qemu_prog_path,
186+
*qemu_cli_args,
187+
*qemu_extra_args,
188+
'-drive',
189+
f'file={image_path},if=mtd,format=raw',
190+
],
87191
**kwargs,
88192
)
89193

194+
def execute_efuse_command(self, command: str):
195+
import espefuse
196+
import pexpect
197+
198+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
199+
s.bind(('127.0.0.1', 0))
200+
_, available_port = s.getsockname()
201+
202+
run_qemu_command = [
203+
'-nographic',
204+
'-machine',
205+
self.app.target,
206+
'-drive',
207+
f'file={self.image_path},if=mtd,format=raw',
208+
'-global',
209+
self.QEMU_STRAP_MODE_FMT.format(self.app.target, QEMU_TARGETS[self.app.target].strap_mode),
210+
'-drive',
211+
f'file={self.efuse_path},if=none,format=raw,id=efuse',
212+
'-global',
213+
f'driver=nvram.{self.app.target}.efuse,property=drive,value=efuse',
214+
'-serial',
215+
f'tcp::{available_port},server,nowait',
216+
]
217+
try:
218+
child = pexpect.spawn(self.qemu_prog_path, run_qemu_command)
219+
res = shlex.split(command)
220+
child.expect('qemu')
221+
222+
res = [r for r in res if r != '--do-not-confirm']
223+
espefuse.main(
224+
[
225+
'--port',
226+
f'socket://localhost:{available_port}',
227+
'--before',
228+
'no-reset',
229+
'--do-not-confirm',
230+
*res,
231+
]
232+
)
233+
self._hard_reset()
234+
finally:
235+
child.terminate()
236+
90237
@property
91238
def qemu_prog_name(self):
92239
if self.app:

pytest-embedded-qemu/tests/test_qemu.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,53 @@
1111
)
1212

1313

14+
@qemu_bin_required
15+
def test_pexpect_write_efuse(testdir):
16+
testdir.makepyfile("""
17+
import pexpect
18+
import pytest
19+
20+
def test_pexpect_by_qemu(dut):
21+
dut.qemu.execute_efuse_command('burn-custom-mac 00:11:22:33:44:55')
22+
dut.expect('')
23+
24+
with open('/tmp/test.test', 'rb') as f:
25+
content = f.read()
26+
27+
expected_output = [
28+
'00000000:00000000000000000000000000800000',
29+
'00000010:00000000000010000000000000000000',
30+
'00000020:00000000000000000000000000000000',
31+
'00000030:00000000000000000000000000000000',
32+
'00000040:00000000000000000000000000000000',
33+
'00000050:000000000000000000000000b8001122',
34+
'00000060:33445500000000000000000000000000',
35+
'00000070:000000010000000000000000'
36+
]
37+
38+
lines = []
39+
for i in range(0, len(content), 16):
40+
line = content[i:i+16]
41+
hex_values = ''.join(f'{byte:02x}' for byte in line)
42+
lines.append(f'{i:08x}:{hex_values}')
43+
assert lines == expected_output
44+
45+
""")
46+
47+
result = testdir.runpytest(
48+
'-s',
49+
'--embedded-services',
50+
'idf,qemu',
51+
'--app-path',
52+
os.path.join(testdir.tmpdir, 'hello_world_esp32'),
53+
'--qemu-cli-args="-machine esp32 -nographic"',
54+
'--qemu-efuse-path',
55+
'/tmp/test.test',
56+
)
57+
58+
result.assert_outcomes(passed=1)
59+
60+
1461
@qemu_bin_required
1562
def test_pexpect_by_qemu_xtensa(testdir):
1663
testdir.makepyfile("""

pytest-embedded-wokwi/pytest_embedded_wokwi/wokwi.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ def __init__(
4747
meta: Meta | None = None,
4848
**kwargs,
4949
):
50+
# Initialize parent class
51+
super().__init__(msg_queue=msg_queue, meta=meta, **kwargs)
52+
5053
self.app = app
5154

5255
# Get Wokwi API token
@@ -71,9 +74,6 @@ def __init__(
7174
self.create_diagram_json()
7275
wokwi_diagram = os.path.join(self.app.app_path, 'diagram.json')
7376

74-
# Initialize parent class
75-
super().__init__(msg_queue=msg_queue, meta=meta, **kwargs)
76-
7777
# Connect and start simulation
7878
try:
7979
flasher_args = firmware_resolver.resolve_firmware(app)

pytest-embedded/pytest_embedded/dut_factory.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def _fixture_classes_and_options_fn(
138138
qemu_prog_path,
139139
qemu_cli_args,
140140
qemu_extra_args,
141+
qemu_efuse_path,
141142
wokwi_diagram,
142143
skip_regenerate_image,
143144
encrypt,
@@ -172,6 +173,7 @@ def _fixture_classes_and_options_fn(
172173
'encrypt': encrypt,
173174
'keyfile': keyfile,
174175
'qemu_prog_path': qemu_prog_path,
176+
'qemu_efuse_path': qemu_efuse_path,
175177
}
176178
)
177179
else:
@@ -303,6 +305,7 @@ def _fixture_classes_and_options_fn(
303305
'qemu_prog_path': qemu_prog_path,
304306
'qemu_cli_args': qemu_cli_args,
305307
'qemu_extra_args': qemu_extra_args,
308+
'qemu_efuse_path': qemu_efuse_path,
306309
'app': None,
307310
'meta': _meta,
308311
'dut_index': dut_index,
@@ -674,6 +677,7 @@ def create(
674677
qemu_prog_path: str | None = None,
675678
qemu_cli_args: str | None = None,
676679
qemu_extra_args: str | None = None,
680+
qemu_efuse_path: str | None = None,
677681
wokwi_diagram: str | None = None,
678682
skip_regenerate_image: bool | None = None,
679683
encrypt: bool | None = None,
@@ -720,6 +724,7 @@ def create(
720724
qemu_prog_path: QEMU program path.
721725
qemu_cli_args: QEMU CLI arguments.
722726
qemu_extra_args: Additional QEMU arguments.
727+
qemu_efuse_path: Efuse binary path.
723728
wokwi_diagram: Wokwi diagram path.
724729
skip_regenerate_image: Skip image regeneration flag.
725730
encrypt: Encryption flag.
@@ -787,6 +792,7 @@ def create(
787792
'qemu_prog_path': qemu_prog_path,
788793
'qemu_cli_args': qemu_cli_args,
789794
'qemu_extra_args': qemu_extra_args,
795+
'qemu_efuse_path': qemu_efuse_path,
790796
'wokwi_diagram': wokwi_diagram,
791797
'skip_regenerate_image': skip_regenerate_image,
792798
'encrypt': encrypt,

pytest-embedded/pytest_embedded/plugin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ def pytest_addoption(parser):
274274
'--qemu-extra-args',
275275
help='QEMU cli extra arguments, will append to the argument list. (Default: None)',
276276
)
277+
qemu_group.addoption(
278+
'--qemu-efuse-path',
279+
help='This option makes it possible to use efuse in QEMU when it is set up.',
280+
)
277281
qemu_group.addoption(
278282
'--skip-regenerate-image',
279283
help='y/yes/true for True and n/no/false for False. '
@@ -963,6 +967,13 @@ def qemu_extra_args(request: FixtureRequest) -> str | None:
963967
return _request_param_or_config_option_or_default(request, 'qemu_extra_args', None)
964968

965969

970+
@pytest.fixture
971+
@multi_dut_argument
972+
def qemu_efuse_path(request: FixtureRequest) -> str | None:
973+
"""Enable parametrization for the same cli option"""
974+
return _request_param_or_config_option_or_default(request, 'qemu_efuse_path', None)
975+
976+
966977
@pytest.fixture
967978
@multi_dut_argument
968979
def skip_regenerate_image(request: FixtureRequest) -> str | None:
@@ -1050,6 +1061,7 @@ def parametrize_fixtures(
10501061
qemu_prog_path,
10511062
qemu_cli_args,
10521063
qemu_extra_args,
1064+
qemu_efuse_path,
10531065
wokwi_diagram,
10541066
skip_regenerate_image,
10551067
encrypt,

0 commit comments

Comments
 (0)