From d241b47284c143510b4c697dfcaef49fa89ba823 Mon Sep 17 00:00:00 2001 From: horw Date: Sat, 11 May 2024 20:23:32 +0800 Subject: [PATCH] feat: add support for efuse in qemu --- .../pytest_embedded_qemu/qemu.py | 98 +++++++++++++++++++ pytest-embedded-qemu/tests/test_qemu.py | 44 +++++++++ .../pytest_embedded/dut_factory.py | 3 + pytest-embedded/pytest_embedded/plugin.py | 12 +++ 4 files changed, 157 insertions(+) diff --git a/pytest-embedded-qemu/pytest_embedded_qemu/qemu.py b/pytest-embedded-qemu/pytest_embedded_qemu/qemu.py index 6b501a1d..80fbd905 100644 --- a/pytest-embedded-qemu/pytest_embedded_qemu/qemu.py +++ b/pytest-embedded-qemu/pytest_embedded_qemu/qemu.py @@ -1,4 +1,6 @@ import asyncio +import binascii +import logging import os import shlex import socket @@ -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( + '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): """ @@ -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, ): @@ -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', + '-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 @@ -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', + ] + 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: diff --git a/pytest-embedded-qemu/tests/test_qemu.py b/pytest-embedded-qemu/tests/test_qemu.py index 7b0b3625..2ce02689 100644 --- a/pytest-embedded-qemu/tests/test_qemu.py +++ b/pytest-embedded-qemu/tests/test_qemu.py @@ -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(""" diff --git a/pytest-embedded/pytest_embedded/dut_factory.py b/pytest-embedded/pytest_embedded/dut_factory.py index cc7444f7..341dd1b1 100644 --- a/pytest-embedded/pytest_embedded/dut_factory.py +++ b/pytest-embedded/pytest_embedded/dut_factory.py @@ -145,6 +145,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, @@ -179,6 +180,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 @@ -287,6 +289,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, diff --git a/pytest-embedded/pytest_embedded/plugin.py b/pytest-embedded/pytest_embedded/plugin.py index 1a977555..660b0d81 100644 --- a/pytest-embedded/pytest_embedded/plugin.py +++ b/pytest-embedded/pytest_embedded/plugin.py @@ -248,6 +248,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. ' @@ -916,6 +920,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]: @@ -1015,6 +1026,7 @@ def parametrize_fixtures( qemu_prog_path, qemu_cli_args, qemu_extra_args, + qemu_efuse_path, wokwi_cli_path, wokwi_timeout, wokwi_scenario,