Skip to content

espemu: eFuses, flash encryption, esp32c5, and a working hard reset (CII-251) - #431

Merged
hfudev merged 3 commits into
espressif:mainfrom
Harshal5:feat/espemu_efuse_and_encryption
Sep 29, 2026
Merged

hfudev merged 3 commits into
espressif:mainfrom
Harshal5:feat/espemu_efuse_and_encryption

Conversation

@Harshal5

@Harshal5 Harshal5 commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Description

The qemu service can give a test an eFuse image, burn eFuses into it and boot an encrypted image. The esp-emu service could do none of that, and it had no dut.serial at all, so ESP-IDF tests that operate on device state had nothing to run against.

eFuses

--espemu-efuse-path gives the emulator an eFuse image, creating a blank QEMU-compatible one when the file does not exist.

execute_efuse_command() runs espefuse against a second emulator instance started in download mode with its UART on a socket. A second instance is needed because the first one is booted into the firmware, and pyserial's socket:// handler ignores modem control lines, so esptool cannot reset it into download mode.

The second instance writes the burned image back as it exits, and the running emulator then reads it in over the control channel with efuse-load, which resets the machine. A reset later in the same test therefore sees the burn, as it would on hardware.

Flash encryption

--encrypt and --keyfile encrypt the merged image the way the qemu service does, with one difference: the command is espsecure encrypt-flash-data --aes-xts, because every target the emulator supports encrypts flash with XTS-AES.

esp32c5

esp-emu has accepted --chip esp32c5 for a while, but the service rejected the target before launching it, so every esp32c5 app was unrunnable under --embedded-services idf,espemu. Nothing else in the service is target specific — the flash image comes from the app's own flash arguments — so the target list was the only gap.

dut.serial

ESP-IDF tests reach the chip through two channels: the console stream (dut.expect) and a control channel (dut.serial) that resets it, erases flash and burns eFuses. Without dut.serial, 48 cases in an ESP-IDF sweep died at setup on AttributeError: ... has no attribute 'serial', and 40% of those only wanted a reset.

esp-emu serves a control channel, and EspEmuSerial sends these over it:

  • hard_reset() sends reset. The emulator process keeps running, so the dut's output stream, its expect history and its log all continue across the reset; relaunching the process would break all three.
  • erase_flash() and erase_region(offset, size) map onto the channel's commands directly.
  • erase_partition(name) resolves the offset and size from the app's partition table, the way IdfSerial does, then erases that region.
  • write_flash_no_enc(offset, path) writes a file into flash.

EspEmuSerial is registered as the serial fixture, like every other service's serial, and is built from a port rather than from the emulator. The factory picks the control port before either fixture exists and hands the same port to both: EspEmu serves it with --control-tcp and EspEmuSerial connects to it. Before choosing a port the factory checks esp-emu --help for --control-tcp, because an older binary exits on an unknown flag.

The operations that need esptool itself — flash(), bootloader_flash() and the partition-writing helpers esp_tee's tests subclass — raise NotImplementedError naming the operation that was wanted.

Validation

Run against ESP-IDF master with --embedded-services idf,espemu:

  • An encrypted image boots on an emulated target with the eFuses burned.
  • esp32c5 collects and passes the same esp_system and esp_hw_support cases as the other targets.
  • esp_driver_uart's test_uart_single_dev, which used to fail at setup on dut.serial, now resets the chip six times and runs through to its test content.
  • nvs_flash's erase_partition('nvs_key') resolves to 0x110000 and erases 0x1000 bytes of the running machine's flash.

In this repo, test_serial_fixture_check.py checks that the serial fixture and dut.serial are the same object, share the emulator's port, and can reset the chip with the port alone.

Dependency

Everything above works with the published esp-emu, which serves --control-tcp, except the efuse-load reload: that command is newer than the channel and not in a release yet. The service checks for it, and without it a burn stays in the image for the next start rather than reaching the machine already running. A build with no control channel at all still boots and runs tests; only the dut.serial operations raise, with an explanation.


Checklist

Before submitting a Pull Request, please ensure the following:

  • 🚨 This PR does not introduce breaking changes.
  • All CI checks (GH Actions) pass.
  • Documentation is updated as needed.
  • Tests are updated or added as necessary.
  • Code is well-commented, especially in complex areas.
  • Git history is clean — commits are squashed to the minimum necessary.

@github-actions github-actions Bot changed the title espemu efuse and flash encryption espemu efuse and flash encryption (CII-251) Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Title Coverage Tests Skipped Failures Errors Time
3.10 ARM64 Coverage 124 23 💤 0 ❌ 0 🔥 13m 35s ⏱️
Espemu Coverage 4 0 💤 0 ❌ 0 🔥 5.767s ⏱️
Qemu Coverage 7 0 💤 0 ❌ 0 🔥 21.153s ⏱️
3.14 X64 Coverage 124 21 💤 0 ❌ 0 🔥 19m 13s ⏱️

@Harshal5 Harshal5 changed the title espemu efuse and flash encryption (CII-251) espemu: eFuses, flash encryption, esp32c5, and a working hard reset (CII-251) Sep 2, 2026
@Harshal5
Harshal5 marked this pull request as draft September 4, 2026 07:22
@Harshal5
Harshal5 force-pushed the feat/espemu_efuse_and_encryption branch from 4db9eb5 to 73de499 Compare September 16, 2026 09:01
@Harshal5
Harshal5 marked this pull request as ready for review September 16, 2026 10:00
@mahavirj
mahavirj requested review from hfudev and a lite review from Copilot September 18, 2026 08:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The eFuse subprocess can leave the running emulator with stale state and overwrite burned data, while --encrypt false is treated as enabled.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds esp-emu support for eFuse images, flash encryption, ESP32-C5, and control-channel resets.

Changes:

  • Adds eFuse CLI configuration and burn support.
  • Adds XTS-AES image encryption.
  • Adds ESP32-C5 support and dut.serial device-state operations.
File summaries
File Description
pytest_embedded/plugin.py Adds the eFuse path option and fixture.
pytest_embedded/dut_factory.py Propagates eFuse and encryption settings.
tests/test_espemu.py Adds eFuse integration coverage.
README.md Documents ESP32-C5 support.
serial.py Implements emulated serial control operations.
espemu.py Adds eFuse handling and control-channel commands.
dut.py Exposes dut.serial.
app.py Adds encrypted image generation.
__init__.py Exports encryption constants and serial support.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pytest-embedded-espemu/pytest_embedded_espemu/espemu.py
Comment thread pytest-embedded/pytest_embedded/dut_factory.py
Comment thread pytest-embedded-espemu/pytest_embedded_espemu/serial.py Outdated
@Harshal5
Harshal5 force-pushed the feat/espemu_efuse_and_encryption branch 2 times, most recently from a81a58d to 4432143 Compare September 23, 2026 05:48
@Harshal5

Copy link
Copy Markdown
Contributor Author

@hfudev PTAL!

Comment thread pytest-embedded-espemu/pytest_embedded_espemu/dut.py Outdated
@Harshal5
Harshal5 force-pushed the feat/espemu_efuse_and_encryption branch from d2910c2 to e1e3f47 Compare September 24, 2026 02:23

@hfudev hfudev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall LGTM. two more small fixes.

Comment thread pytest-embedded/pytest_embedded/dut_factory.py Outdated
Comment thread pytest-embedded/pytest_embedded/plugin.py Outdated
@hfudev

hfudev commented Sep 25, 2026

Copy link
Copy Markdown
Member

btw, please also cleanup the commit history a bit. we're generating the changelog based on the conventional commit history :)

`--espemu-efuse-path` gives the emulator an eFuse image, and
`execute_efuse_command()` burns into it through a second instance in
download mode. `--encrypt` and `--keyfile` encrypt the merged image the
way the qemu service does.
esp-emu has accepted `--chip esp32c5` for a while, but the service's
target list rejected it before launch, so every esp32c5 app was
unrunnable under `idf,espemu`.
`EspEmuSerial` is the `serial` fixture, built from the emulator's control
port. It resets the chip and erases and writes flash over that channel
without restarting the process, and a burned eFuse image is reloaded into
the running emulator. Operations that need esptool still raise.
@Harshal5
Harshal5 force-pushed the feat/espemu_efuse_and_encryption branch from e1e3f47 to cf330dd Compare September 25, 2026 09:58
@Harshal5

Copy link
Copy Markdown
Contributor Author

btw, please also cleanup the commit history a bit. we're generating the changelog based on the conventional commit history :)

Done, PTAL again!

@hfudev
hfudev merged commit 2633b01 into espressif:main Sep 29, 2026
10 of 11 checks passed
@hfudev

hfudev commented Sep 29, 2026

Copy link
Copy Markdown
Member

LGTM. Thank you :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants