[zephyr_coredump] Store a coredump in flash on nRF52 and print it to the next log reader (#13122)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
tomaszduda23
2026-10-17 19:22:28 +00:00
committed by GitHub
co-authored by Copilot J. Nick Koston
parent c2c8cb5eb3
commit 5f8f075e76
16 changed files with 526 additions and 14 deletions
+1
View File
@@ -727,6 +727,7 @@ esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23
esphome/components/zephyr_coredump/* @tomaszduda23
esphome/components/zephyr_mcumgr/ota/* @tomaszduda23
esphome/components/zephyr_pwm/* @wiomoc
esphome/components/zhlt01/* @cfeenstra1024
+31 -7
View File
@@ -3,6 +3,9 @@
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "logger.h"
#ifdef USE_CRASH_HANDLER
#include "esphome/components/zephyr_coredump/coredump.h"
#endif
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
@@ -12,12 +15,6 @@
#include <esphome/components/zephyr/reset_reason.h>
#endif
namespace esphome::zephyr_coredump {
__attribute__((weak)) void print_coredump() {}
} // namespace esphome::zephyr_coredump
namespace esphome::logger {
// Zephyr 3.7 renamed z_arch_esf_t to struct arch_esf; the old name was later removed.
@@ -56,6 +53,14 @@ void Logger::cdc_loop_() {
if (!opened) {
App.schedule_dump_config();
}
#ifdef USE_CRASH_HANDLER
// The new reader also gets a coredump stored by the last crash; a closed port keeps it for the next one
if (!opened) {
log_crash_to_client();
} else {
zephyr_coredump::stop_coredump();
}
#endif
opened = !opened;
}
#endif
@@ -103,7 +108,16 @@ void Logger::pre_setup() {
const char *reset_reason = zephyr::get_reset_reason(std::span<char, zephyr::RESET_REASON_BUFFER_SIZE>(reason_buffer));
ESP_LOGI(TAG, "Reset reason: %s", reset_reason);
dump_crash_();
zephyr_coredump::print_coredump();
#endif
#ifdef USE_CRASH_HANDLER
#ifdef USE_LOGGER_UART_SELECTION_USB_CDC
// Nobody reads a USB port this early; cdc_loop_() reports the dump once the port is opened
if (this->uart_ == UART_SELECTION_USB_CDC)
return;
#endif
// Without a UART (baud_rate: 0, or one that did not come up) nothing reaches the console
if (this->uart_dev_ != nullptr)
log_crash_to_client();
#endif
}
@@ -195,6 +209,16 @@ void k_sys_fatal_error_handler(unsigned int reason, const FatalErrorEsf *esf) {
arch_restart();
}
#ifdef USE_CRASH_HANDLER
bool crash_log_pending() { return zephyr_coredump::coredump_pending(); }
void log_crash_to_client() {
// A level that drops the lines would erase the dump unseen; keep it for a client that can see it
if (global_logger->get_log_level(zephyr_coredump::CRASH_LOG_TAG) < ESPHOME_LOG_LEVEL_ERROR)
return;
zephyr_coredump::print_coredump();
}
#endif
} // namespace esphome::logger
extern "C" {
+1
View File
@@ -93,6 +93,7 @@ CONFIG_NVS=y
CONFIG_SETTINGS=y
""",
),
_TidySubsystem("coredump", "CONFIG_DEBUG_COREDUMP=y\n"),
)
_TIDY_PRJ_CONF = "".join(
@@ -0,0 +1,111 @@
import esphome.codegen as cg
from esphome.components.nrf52 import NRF52840_FLASH_SIZE, TESTING_FLASH_SIZE
from esphome.components.nrf52.boards import BOARDS_ZEPHYR, BOOTLOADER_CONFIG
from esphome.components.zephyr import (
Section,
zephyr_add_overlay,
zephyr_add_pm_static,
zephyr_add_prj_conf,
zephyr_data,
)
from esphome.components.zephyr.const import (
BOOTLOADER_MCUBOOT,
KEY_BOARD,
KEY_BOOTLOADER,
KEY_PM_STATIC,
)
import esphome.config_validation as cv
from esphome.const import CONF_ID, KEY_CORE, KEY_FRAMEWORK_VERSION
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.types import ConfigType
CODEOWNERS = ["@tomaszduda23"]
DEPENDENCIES = ["logger"]
DOMAIN = "zephyr_coredump"
Coredump = cg.esphome_ns.namespace(DOMAIN).class_("Coredump", cg.Component)
# SDK 2.9.2 and later can store every thread's stack; older ones only the faulting thread
DUMP_THREADS_MIN_VERSION = cv.Version(2, 9, 2)
# Zephyr 4.0 (from SDK 3.0) carries the fault stack pointer fix (zephyr PR 79622)
SP_FIX_MIN_VERSION = cv.Version(3, 0, 0)
# One size for every SDK, so enabling the component moves the OTA image slots the same way everywhere
PARTITION_SIZE = 0x8000
CONFIG_SCHEMA = cv.All(
cv.Schema({cv.GenerateID(): cv.declare_id(Coredump)}).extend(cv.COMPONENT_SCHEMA),
cv.only_on_nrf52,
)
def _framework_version() -> cv.Version:
return CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
def _partition_address() -> int:
"""Place the partition directly below the lowest section reserved above the
SoftDevice (the bootloader), or at the end of flash with MCUboot."""
data = zephyr_data()
# The SoftDevice (or MBR) is the only section that can start at 0
reserved = [s.address for s in data[KEY_PM_STATIC] if s.address > 0]
if reserved:
return min(reserved) - PARTITION_SIZE
if (bootloader := data[KEY_BOOTLOADER]) != BOOTLOADER_MCUBOOT:
raise cv.Invalid(
f"{DOMAIN} does not know the flash layout of bootloader '{bootloader}', "
f"select one of: {', '.join(BOOTLOADER_CONFIG)}"
)
# MCUboot reserves nothing, so the flash size has to come from the board
board = data[KEY_BOARD]
if "nrf52840" not in board and board not in BOARDS_ZEPHYR:
raise cv.Invalid(
f"{DOMAIN} does not know the flash size of board '{board}', "
"only nRF52840 boards are supported"
)
flash_size = TESTING_FLASH_SIZE if CORE.testing_mode else NRF52840_FLASH_SIZE
return flash_size - PARTITION_SIZE
def _final_validate(config: ConfigType) -> ConfigType:
_partition_address()
return config
FINAL_VALIDATE_SCHEMA = _final_validate
# Runs before zephyr_mcumgr OTA (OTA_UPDATES), which sizes its image slots below this partition
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
async def to_code(config: ConfigType) -> None:
# The logger's crash handler hooks: the dump reaches serial, API and MQTT log clients
cg.add_define("USE_CRASH_HANDLER")
zephyr_add_prj_conf("DEBUG_COREDUMP", True)
zephyr_add_prj_conf("DEBUG_COREDUMP_BACKEND_FLASH_PARTITION", True)
framework_ver = _framework_version()
if framework_ver >= DUMP_THREADS_MIN_VERSION:
zephyr_add_prj_conf("DEBUG_COREDUMP_MEMORY_DUMP_THREADS", True)
else:
zephyr_add_prj_conf("DEBUG_COREDUMP_MEMORY_DUMP_MIN", True)
if framework_ver < SP_FIX_MIN_VERSION:
cg.add_define("USE_ZEPHYR_COREDUMP_SP_FIX")
cg.add_build_flag("-Wl,--wrap=z_arm_fatal_error")
cg.add_build_flag("-Wl,--wrap=z_arm_fault")
zephyr_add_prj_conf("PARTITION_MANAGER_ENABLED", True)
addr = _partition_address()
zephyr_add_pm_static(
[Section("coredump_partition", addr, PARTITION_SIZE, "flash_primary")]
)
zephyr_add_overlay(
f"""
&flash0 {{
partitions {{
coredump_partition: partition@{addr:x} {{
label = "coredump-partition";
reg = <{addr:#x} {PARTITION_SIZE:#x}>;
}};
}};
}};
"""
)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -0,0 +1,191 @@
#ifdef USE_ZEPHYR
#include "coredump.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
#include <zephyr/version.h>
// The header has no C++ linkage guards of its own
extern "C" {
#include <zephyr/debug/coredump.h>
}
namespace esphome::zephyr_coredump {
ESPHOME_LOG_TAG(TAG, "zephyr_coredump");
// Lines in the "#CD:" format that Zephyr's coredump_serial_log_parser.py reads: the log header (time, level,
// tag, line number and colour codes, under 64 characters), "#CD:" and two hex characters per byte must fit the
// logger's line buffer, so a small buffer gets shorter lines
static constexpr size_t LINE_OVERHEAD = 64;
static constexpr size_t CHUNK_SIZE =
std::min<size_t>(64, (ESPHOME_LOGGER_TX_BUFFER_SIZE - LINE_OVERHEAD) / 2 / 16 * 16);
static_assert(CHUNK_SIZE >= 16, "logger line buffer too small for a coredump line");
// A USB host starts reading the port only some 100 ms after opening it; until then the CDC driver discards output
static constexpr uint32_t START_DELAY_MS = 1000;
static constexpr uint32_t START_DELAY_ID = 0;
// Between lines, so a loop that runs faster than usual (a stepper moving) cannot overrun the USB CDC ring buffer
static constexpr uint32_t LINE_INTERVAL_MS = 10;
// On a UART the logger reports the reader before this component exists, so the flag outlives the pointer
static Coredump *global_coredump = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool reader_present = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
bool coredump_pending() { return global_coredump != nullptr && global_coredump->stored_; }
void print_coredump() {
reader_present = true;
if (global_coredump != nullptr)
global_coredump->start_print_();
}
void stop_coredump() {
reader_present = false;
if (global_coredump != nullptr)
global_coredump->stop_print_();
}
void Coredump::setup() {
global_coredump = this;
this->disable_loop();
// The size comes from the header alone; "has stored dump" also checks the checksum and would hide a
// corrupt dump instead of letting it be reported and erased
const int size = coredump_query(COREDUMP_QUERY_GET_STORED_DUMP_SIZE, nullptr);
if (size <= 0) {
return;
}
this->stored_ = true;
this->total_ = size;
// A bad dump is reported and erased from loop() like a good one, once somebody can read the message
this->verify_ = coredump_cmd(COREDUMP_CMD_VERIFY_STORED_DUMP, nullptr);
if (reader_present)
this->start_print_();
}
// A new reader gets the whole dump: a print cut short by the port closing starts over
void Coredump::start_print_() {
if (!this->stored_ || this->printing_)
return;
this->printing_ = true;
this->offset_ = 0;
this->set_timeout(START_DELAY_ID, START_DELAY_MS, [this]() { this->enable_loop(); });
}
void Coredump::stop_print_() {
this->printing_ = false;
this->cancel_timeout(START_DELAY_ID);
this->disable_loop();
}
void Coredump::loop() {
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_line_ms_ < LINE_INTERVAL_MS)
return;
this->last_line_ms_ = now;
if (this->verify_ != 1) {
ESP_LOGE(TAG, "Stored coredump verification failed: %d", this->verify_);
this->finish_(0);
return;
}
if (this->offset_ == 0) {
ESP_LOGE(TAG, "#CD:BEGIN#");
}
uint8_t buf[CHUNK_SIZE];
char hex[format_hex_size(CHUNK_SIZE)];
coredump_cmd_copy_arg arg{static_cast<off_t>(this->offset_), buf,
std::min<size_t>(CHUNK_SIZE, this->total_ - this->offset_)};
const int ret = coredump_cmd(COREDUMP_CMD_COPY_STORED_DUMP, &arg);
if (ret > 0) {
ESP_LOGE(TAG, "#CD:%s", format_hex_to(hex, buf, ret));
this->offset_ += ret;
}
if (ret <= 0 || this->offset_ >= this->total_) {
ESP_LOGE(TAG, "#CD:END#");
this->finish_(ret < 0 ? ret : 0);
}
}
// Erase so the dump is not reported again; the next crash writes a new one. Erasing the partition halts the
// CPU for about 85 ms per 4 KB page, so this one call takes most of a second
void Coredump::finish_(int err) {
if (err < 0) {
ESP_LOGE(TAG, "Failed to copy coredump: %d", err);
}
const int erase = coredump_cmd(COREDUMP_CMD_ERASE_STORED_DUMP, nullptr);
if (erase < 0) {
ESP_LOGE(TAG, "Failed to erase coredump: %d", erase);
}
this->stored_ = false;
this->total_ = 0;
this->verify_ = 0;
this->printing_ = false;
this->disable_loop();
}
void Coredump::dump_config() {
ESP_LOGCONFIG(TAG,
"Coredump:\n"
" Stored dump: %s\n"
" Size: %" PRIu32 "\n"
" Verified: %s\n"
" Error: %d",
YESNO(this->stored_), this->total_, YESNO(this->verify_ == 1),
coredump_query(COREDUMP_QUERY_GET_ERROR, nullptr));
}
} // namespace esphome::zephyr_coredump
// Backport of https://github.com/zephyrproject-rtos/zephyr/pull/79622 (Zephyr 4.0): give GDB the stack
// pointer from before the exception frame so it can unwind. The Python side links it in with --wrap.
#ifdef USE_ZEPHYR_COREDUMP_SP_FIX
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
extern "C" {
#if KERNEL_VERSION_NUMBER >= 0x030700
using esf_t = struct arch_esf;
#else
using esf_t = z_arch_esf_t;
#endif
static constexpr uint8_t EXC_RETURN_STACK_FRAME_TYPE_POS = 4;
static constexpr uint32_t EXC_RETURN_STACK_FRAME_TYPE_EXTENDED = 0;
static uint32_t global_exc_return;
void __real_z_arm_fault(uint32_t msp, uint32_t psp, uint32_t exc_return, _callee_saved_t *callee_regs);
void __wrap_z_arm_fault(uint32_t msp, uint32_t psp, uint32_t exc_return, _callee_saved_t *callee_regs) {
global_exc_return = exc_return;
__real_z_arm_fault(msp, psp, exc_return, callee_regs);
}
// Faults from z_arm_fault() come here with the frame z_arm_fault() saved; a spurious interrupt has none.
// Software fatal errors (k_panic, k_oops) reach z_arm_fatal_error() inside its own object, which --wrap cannot
// intercept, so their dumps keep Zephyr's stack pointer.
void __real_z_arm_fatal_error(unsigned int reason, const esf_t *esf);
void __wrap_z_arm_fatal_error(unsigned int reason, const esf_t *esf) {
#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) || defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
if (esf == nullptr) {
__real_z_arm_fatal_error(reason, esf);
return;
}
z_arm_coredump_fault_sp += sizeof(esf->basic);
#if defined(CONFIG_FPU) && defined(CONFIG_FPU_SHARING)
// The thread used the FP registers, so the frame also holds them
if ((global_exc_return & BIT(EXC_RETURN_STACK_FRAME_TYPE_POS)) == EXC_RETURN_STACK_FRAME_TYPE_EXTENDED) {
z_arm_coredump_fault_sp += sizeof(esf->fpu);
}
#endif
#ifndef CONFIG_ARMV8_M_MAINLINE
// PSR bit 9 set: the frame was padded for alignment
if ((esf->basic.xpsr & SCB_CCR_STKALIGN_Msk) == SCB_CCR_STKALIGN_Msk) {
z_arm_coredump_fault_sp |= 0x4;
}
#endif
#endif
__real_z_arm_fatal_error(reason, esf);
}
}
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
#endif
#endif
@@ -0,0 +1,43 @@
#pragma once
#include "esphome/core/component.h"
namespace esphome::zephyr_coredump {
/// The tag the dump is logged under, so a client can check the level in force for it
inline constexpr const char *CRASH_LOG_TAG = "zephyr_coredump";
/// Whether a dump from before this boot is waiting to be printed.
bool coredump_pending();
/// A log reader is present: serial at boot, USB CDC when the port opens, an API or MQTT client subscribing.
/// The stored dump is printed from the start and erased once it has gone out; a print already under way
/// carries on.
void print_coredump();
/// The reader went away (the USB port was closed); the print stops and the dump stays for the next one.
/// Readers are not told apart: closing the USB port also stops a print an API or MQTT client is receiving,
/// and a client that disconnects does not stop one, the same as the other platforms' crash records.
void stop_coredump();
class Coredump : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
protected:
friend bool coredump_pending();
friend void print_coredump();
friend void stop_coredump();
/// Start printing after a short delay, one line per loop so the USB CDC transmit buffer and the main loop keep up
void start_print_();
void stop_print_();
void finish_(int err);
uint32_t offset_{0};
uint32_t total_{0};
uint32_t last_line_ms_{0};
int verify_{0}; // result of verifying the stored dump, 1 when it is good
bool stored_{false};
bool printing_{false};
};
} // namespace esphome::zephyr_coredump
@@ -1,3 +1,5 @@
import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components.nrf52.boards import MCUMGR_OTA_BOOTLOADERS
@@ -31,8 +33,11 @@ from esphome.const import (
)
from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
import esphome.final_validate as fv
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@tomaszduda23"]
DEPENDENCIES = ["zephyr"]
@@ -114,6 +119,12 @@ def _validate_bootloader(config: ConfigType) -> None:
def _final_validate(config: ConfigType) -> None:
_validate_ble_server(config)
_validate_bootloader(config)
if "zephyr_coredump" in fv.full_config.get():
_LOGGER.warning(
"zephyr_coredump moves the OTA image slots. A device that already runs mcumgr OTA "
"needs one flash over USB after enabling or disabling it; an OTA update alone leaves "
"the installed MCUboot with the old slot layout"
)
if (uart := config[CONF_TRANSPORT].get(CONF_HARDWARE_UART)) is not None:
cdc_id = UARTS[uart][1]
if cdc_id >= 0:
@@ -210,17 +221,18 @@ async def to_code(config: ConfigType) -> None:
bootloader = zephyr_data()[KEY_BOOTLOADER]
if bootloader != BOOTLOADER_MCUBOOT:
# Derive partition addresses from the SoftDevice and bootloader sections
# registered with the Partition Manager (moved in testing mode) so that
# the DTS flash map matches what it produces:
# MCUboot sits immediately after the SoftDevice, then slot0, then slot1.
# Derive partition addresses from the sections registered with the
# Partition Manager (moved in testing mode) so that the DTS flash map
# matches what it produces:
# MCUboot sits immediately after the SoftDevice, then slot0, then slot1,
# which ends at the lowest section reserved above (bootloader, coredump).
sections = zephyr_data()[KEY_PM_STATIC]
mcuboot_size = 0x9000
sd_end = next(s.address + s.size for s in sections if "SoftDevice" in s.name)
bl_start = next(s.address for s in sections if "Adafruit" in s.name)
sd_end = next(s.end_address for s in sections if "SoftDevice" in s.name)
top = min(s.address for s in sections if s.address >= sd_end)
slot0_start = sd_end + mcuboot_size
# Align slot size down to a 4 KB sector boundary
slot_size = ((bl_start - slot0_start) // 2 // 0x1000) * 0x1000
slot_size = ((top - slot0_start) // 2 // 0x1000) * 0x1000
slot1_start = slot0_start + slot_size
def _mcuboot_partition_overlay() -> str:
+1
View File
@@ -756,6 +756,7 @@
#define USE_SOCKET_IMPL_BSD_SOCKETS
#define USE_SOFTDEVICE_ID 7
#define USE_SOFTDEVICE_VERSION 1
#define USE_ZEPHYR_COREDUMP_SP_FIX
#define USE_ZIGBEE
#define USE_ZIGBEE_WIPE_ON_BOOT
#define USE_ZIGBEE_WIPE_ON_BOOT_MAGIC 1
@@ -0,0 +1,15 @@
esphome:
name: coredumptest
nrf52:
board: adafruit_itsybitsy_nrf52840
bootloader: adafruit_nrf52_sd140_v6
logger:
zephyr_ble_server:
ota:
- platform: zephyr_mcumgr
zephyr_coredump:
@@ -0,0 +1,104 @@
"""Tests for the placement of the zephyr_coredump flash partition."""
from collections.abc import Callable
import logging
from pathlib import Path
import re
import pytest
from esphome.components.nrf52 import set_core_data
from esphome.components.zephyr import zephyr_data
from esphome.components.zephyr.const import KEY_OVERLAY, KEY_PM_STATIC
from esphome.components.zephyr_coredump import (
DUMP_THREADS_MIN_VERSION,
PARTITION_SIZE,
_partition_address,
)
import esphome.config_validation as cv
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
from esphome.core import CORE
def _core_data(
board: str,
bootloader: str,
testing_mode: bool = False,
version: cv.Version = DUMP_THREADS_MIN_VERSION,
) -> None:
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: version}
CORE.testing_mode = testing_mode
set_core_data({"board": board, "bootloader": bootloader})
@pytest.mark.parametrize(
("board", "bootloader", "testing_mode", "expected"),
[
# Below the Adafruit bootloader at 0xF4000
("adafruit_itsybitsy_nrf52840", "adafruit_nrf52_sd140_v6", False, 0xEC000),
# Below the factory Open DFU bootloader at 0xE0000
("nrf52840dongle", "nrf", False, 0xD8000),
# Nothing reserved: the end of the 1 MB flash
("adafruit_feather_nrf52840", "mcuboot", False, 0xF8000),
# An nRF52840 board whose name does not say so
("xiao_ble", "mcuboot", False, 0xF8000),
# Testing mode moves the bootloader to the end of the faked 4 MB flash
("adafruit_itsybitsy_nrf52840", "adafruit_nrf52_sd140_v6", True, 0x3EC000),
("adafruit_feather_nrf52840", "mcuboot", True, 0x3F8000),
],
)
def test_partition_address(
board: str, bootloader: str, testing_mode: bool, expected: int
) -> None:
_core_data(board, bootloader, testing_mode)
assert _partition_address() == expected
assert PARTITION_SIZE % 0x1000 == 0
def test_partition_size_is_the_same_on_every_sdk() -> None:
_core_data("adafruit_feather_nrf52840", "mcuboot", version=cv.Version(2, 6, 1))
assert _partition_address() == 0x100000 - PARTITION_SIZE
def test_unknown_flash_size_rejected() -> None:
_core_data("bbc_microbit", "mcuboot")
with pytest.raises(cv.Invalid, match="only nRF52840 boards are supported"):
_partition_address()
def test_bootloader_without_layout_rejected() -> None:
"""The generic adafruit bootloader registers no sections, so the partition would
land inside the bootloader if it were treated like MCUboot."""
_core_data("adafruit_itsybitsy_nrf52840", "adafruit")
with pytest.raises(cv.Invalid, match="flash layout of bootloader 'adafruit'"):
_partition_address()
def test_mcumgr_slot_ends_below_partition(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""The OTA image slot1 must end at or before the coredump partition."""
generate_main(component_config_path("mcumgr_adafruit.yaml"))
# The logger's crash handler hooks are compiled in for the dump
assert any(d.name == "USE_CRASH_HANDLER" for d in CORE.defines)
sections = {s.name: s for s in zephyr_data()[KEY_PM_STATIC]}
coredump = sections["coredump_partition"]
assert coredump.address == 0xEC000
match = re.search(
r"slot1_partition: partition@\w+ \{\s*reg = <(0x\w+) (0x\w+)>",
zephyr_data()[KEY_OVERLAY][""],
)
assert match is not None
start, size = (int(v, 16) for v in match.groups())
assert start + size <= coredump.address
def test_mcumgr_warns_that_the_slots_move(
caplog: pytest.LogCaptureFixture,
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
with caplog.at_level(logging.WARNING):
generate_main(component_config_path("mcumgr_adafruit.yaml"))
assert "zephyr_coredump moves the OTA image slots" in caplog.text
@@ -0,0 +1 @@
zephyr_coredump:
@@ -0,0 +1,2 @@
packages:
zephyr_coredump: !include common.yaml
@@ -0,0 +1,2 @@
packages:
zephyr_coredump: !include common.yaml
@@ -0,0 +1,2 @@
packages:
zephyr_coredump: !include common.yaml
@@ -0,0 +1,2 @@
packages:
zephyr_coredump: !include common.yaml