Memfault Firmware SDK 1.10.0 (Build 8899)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04b31fa..8b80ea4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,34 @@
 and this project adheres to
 [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.10.0] - 2024-07-12
+
+### :chart_with_upwards_trend: Improvements
+
+- Zephyr:
+
+  - Add support for Memfault Logging when `CONFIG_LOG_MODE_MINIMAL=y`, in
+    addition to the previously supported `LOG_MODE_DEFERRED` (Zephyr default)
+    and `LOG_MODE_IMMEDIATE`.
+    [Zephyr minimal log mode](https://docs.zephyrproject.org/3.6.0/kconfig.html#CONFIG_LOG_MODE_MINIMAL)
+    disables all backends- logs are simply sent to `printk`.
+
+  - For ESP32 chips, place the reboot reason tracking into a dedicated section,
+    `.rtc_noinit`, to ensure the reboot reason is preserved across reboots. This
+    was previously only supported on Zephyr ESP32-S3/S2 devices (all ESP32
+    devices on ESP-IDF already support this).
+
+  - Fix a bug where the default `memfault_platform_get_device_info()`, which
+    uses the hwinfo subsystem, when available, was incorrectly disabled when
+    `CONFIG_MEMFAULT_REBOOT_REASON_GET_CUSTOM=y`.
+
+- General:
+
+  - Improve the `memfault_demo_cli_cmd_busfault()` function (accessed via
+    `test_busfault`/`mflt test busfault` demo CLI commands) to produce BusFaults
+    on more devices. Previously, certain chips would either not produce a fault,
+    or would produce a MemManage exception instead.
+
 ## [1.9.4] - 2024-07-01
 
 ### :chart_with_upwards_trend: Improvements
diff --git a/VERSION b/VERSION
index bc6021f..cd7a1ab 100644
--- a/VERSION
+++ b/VERSION
@@ -1,3 +1,3 @@
-BUILD ID: 8676
-GIT COMMIT: 69ac2e1558
-VERSION: 1.9.4
+BUILD ID: 8899
+GIT COMMIT: 7e534eec9c
+VERSION: 1.10.0
diff --git a/components/demo/src/panics/memfault_demo_panics.c b/components/demo/src/panics/memfault_demo_panics.c
index fa60953..2fb0ca9 100644
--- a/components/demo/src/panics/memfault_demo_panics.c
+++ b/components/demo/src/panics/memfault_demo_panics.c
@@ -172,8 +172,13 @@
 }
 
 int memfault_demo_cli_cmd_busfault(MEMFAULT_UNUSED int argc, MEMFAULT_UNUSED char *argv[]) {
-  void (*unaligned_func)(void) = (void (*)(void))0x50000001;
-  unaligned_func();
+  // Trigger a BusFault by attempting to load and jump to an address from the "RAM" (0x60000000 -
+  // 0x7FFFFFFF) region, described as: "Memory with write-back, write allocate cache attribute for
+  // L2/L3 cache support." in the ARMv7-M architecture reference manual. This is not guaranteed to
+  // trigger a BusFault, if the address is both loadable and executable, but on many devices it
+  // will.
+  void (*busfault_func)(void) = (void (*)(void))0x60000001;
+  busfault_func();
 
   // We should never get here -- platforms BusFault or HardFault handler should be tripped
   // with a precise error due to unaligned execution
diff --git a/components/include/memfault/version.h b/components/include/memfault/version.h
index b104c82..2c30904 100644
--- a/components/include/memfault/version.h
+++ b/components/include/memfault/version.h
@@ -19,8 +19,8 @@
   uint8_t patch;
 } sMfltSdkVersion;
 
-#define MEMFAULT_SDK_VERSION   { .major = 1, .minor = 9, .patch = 4 }
-#define MEMFAULT_SDK_VERSION_STR "1.9.4"
+#define MEMFAULT_SDK_VERSION   { .major = 1, .minor = 10, .patch = 0 }
+#define MEMFAULT_SDK_VERSION_STR "1.10.0"
 
 #ifdef __cplusplus
 }
diff --git a/examples/freertos/boards/qemu_mps2_an385/startup.c b/examples/freertos/boards/qemu_mps2_an385/startup.c
index 8b4d4a3..33bb706 100644
--- a/examples/freertos/boards/qemu_mps2_an385/startup.c
+++ b/examples/freertos/boards/qemu_mps2_an385/startup.c
@@ -102,6 +102,10 @@
 }
 
 __attribute__((noreturn)) void Reset_Handler(void) {
+// enable mem/bus/usage faults
+#define SCBSHCSR_ (*(uint32_t *)0xE000ED24)
+  SCBSHCSR_ |= (1UL << 16U) | (1UL << 17U) | (1UL << 18U);
+
   prv_cinit();
 
   uart_init();
diff --git a/examples/freertos/boards/qemu_mps2_an386/startup.c b/examples/freertos/boards/qemu_mps2_an386/startup.c
index 8b4d4a3..6605472 100644
--- a/examples/freertos/boards/qemu_mps2_an386/startup.c
+++ b/examples/freertos/boards/qemu_mps2_an386/startup.c
@@ -102,6 +102,10 @@
 }
 
 __attribute__((noreturn)) void Reset_Handler(void) {
+  // enable mem/bus/usage faults
+#define SCBSHCSR_ (*(uint32_t *)0xE000ED24)
+  SCBSHCSR_ |= (1UL << 16U) | (1UL << 17U) | (1UL << 18U);
+
   prv_cinit();
 
   uart_init();
diff --git a/ports/zephyr/common/CMakeLists.txt b/ports/zephyr/common/CMakeLists.txt
index 866c94c..9207a3b 100644
--- a/ports/zephyr/common/CMakeLists.txt
+++ b/ports/zephyr/common/CMakeLists.txt
@@ -36,7 +36,10 @@
   #    https://github.com/zephyrproject-rtos/zephyr/pull/31535
   # It fully replaced version 1 after the 3.1 release: https://github.com/zephyrproject-rtos/zephyr/issues/46500
   # and the option to enable logging v1 via CONFIG_LOG1 was entirely removed
-  if(${KERNEL_VERSION_MAJOR}.${KERNEL_VERSION_MINOR}.${KERNEL_PATCHLEVEL} VERSION_GREATER_EQUAL "3.1.99" OR CONFIG_LOG2)
+  if(CONFIG_LOG_MODE_MINIMAL)
+    target_link_libraries(app INTERFACE "-Wl,--wrap=z_log_minimal_printk")
+    zephyr_library_sources(memfault_logging_minimal.c)
+  elseif(${KERNEL_VERSION_MAJOR}.${KERNEL_VERSION_MINOR}.${KERNEL_PATCHLEVEL} VERSION_GREATER_EQUAL "3.1.99" OR CONFIG_LOG2)
     zephyr_library_sources(memfault_logging.c)
   else()
     zephyr_library_sources(memfault_logging_legacy.c)
@@ -73,4 +76,8 @@
   zephyr_ld_options(-Wl,--wrap=k_free)
 endif()
 
+if(CONFIG_SOC_FAMILY_ESP32)
+  zephyr_linker_sources(SECTIONS memfault-rtc-noinit-region.ld)
+endif()
+
 zephyr_include_directories(.)
diff --git a/ports/zephyr/common/memfault-rtc-noinit-region.ld b/ports/zephyr/common/memfault-rtc-noinit-region.ld
new file mode 100644
index 0000000..26d6309
--- /dev/null
+++ b/ports/zephyr/common/memfault-rtc-noinit-region.ld
@@ -0,0 +1,19 @@
+/**
+* For ESP32 variants other than ESP32-S3/S2, provide a region of noinit placed
+in * RTC memory, for holding reboot reason information through OTA updates. *
+The normal Zephyr noinit region is not suitable for this, because its offset *
+can change through an OTA update, invalidating the reboot reason data.
+ *
+ * Unfortunately this doesn't place the section in the same order in RTC as in
+ * the ESP32-S3/S2, but it puts it in a usable location.
+ */
+#if !defined(CONFIG_SOC_SERIES_ESP32S3) && !defined(CONFIG_SOC_SERIES_ESP32S2)
+SECTION_PROLOGUE(.rtc_noinit,(NOLOAD),)
+{
+    . = ALIGN(4);
+    _rtc_noinit_start = ABSOLUTE(.);
+    *(.rtc_noinit .rtc_noinit.*)
+    . = ALIGN(4) ;
+    _rtc_noinit_end = ABSOLUTE(.);
+} GROUP_LINK_IN(rtc_slow_seg)
+#endif
diff --git a/ports/zephyr/common/memfault_logging_minimal.c b/ports/zephyr/common/memfault_logging_minimal.c
new file mode 100644
index 0000000..7f84ac5
--- /dev/null
+++ b/ports/zephyr/common/memfault_logging_minimal.c
@@ -0,0 +1,73 @@
+//! @file
+//!
+//! Copyright (c) Memfault, Inc.
+//! See License.txt for details
+//!
+//! @brief
+//! Hooks the Memfault log collection to the Zephyr LOG_MODE_MINIMAL logging
+//! system.
+
+// clang-format off
+#include MEMFAULT_ZEPHYR_INCLUDE(kernel.h)
+#include MEMFAULT_ZEPHYR_INCLUDE(sys/atomic.h)
+#include MEMFAULT_ZEPHYR_INCLUDE(logging/log.h)
+
+#include <stdarg.h>
+
+#include "memfault/components.h"
+// clang-format on
+
+enum {
+  MFLT_LOG_BUFFER_BUSY,
+};
+static atomic_t s_log_output_busy_flag;
+
+static void prv_log_init(void) {
+  // Static RAM storage where logs will be stored.
+  static uint8_t s_mflt_log_buf_storage[CONFIG_MEMFAULT_LOGGING_RAM_SIZE];
+  memfault_log_boot(s_mflt_log_buf_storage, sizeof(s_mflt_log_buf_storage));
+}
+
+static void prv_memfault_log_vprintf(const char *fmt, va_list args) {
+  // Force synchronization at this level.
+  if (atomic_test_and_set_bit(&s_log_output_busy_flag, MFLT_LOG_BUFFER_BUSY)) {
+    return;
+  }
+
+  // This is idempotent; but sensitive to concurrent access. The atomic flag
+  // that protects this function should be good enough, since it only has to
+  // succeed once. The vulnerability is only if a user is accessing
+  // memfault_log_save_preformatted_nolock() directly (should not be done).
+  prv_log_init();
+
+  // Render the log message into a buffer. Static storage class, so it doesn't
+  // contribute to stack usage which could be problematic in this call site.
+  static uint8_t s_zephyr_render_buf[128];
+  int save_length = vsnprintk(s_zephyr_render_buf, sizeof(s_zephyr_render_buf), fmt, args);
+  if (save_length > 0) {
+    // Always set to info, since we can't reliably determine the log level
+    memfault_log_save_preformatted_nolock(kMemfaultPlatformLogLevel_Info, s_zephyr_render_buf,
+                                          save_length);
+  }
+
+  atomic_clear_bit(&s_log_output_busy_flag, MFLT_LOG_BUFFER_BUSY);
+}
+
+void __wrap_z_log_minimal_printk(const char *fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+
+  prv_memfault_log_vprintf(fmt, args);
+
+  va_start(args, fmt);
+
+  // Now call zephyr printk
+  z_log_minimal_vprintk(fmt, args);
+
+  va_end(args);
+}
+
+// Ensure the substituted function signature matches the original function
+_Static_assert(__builtin_types_compatible_p(__typeof__(&z_log_minimal_printk),
+                                            __typeof__(&__wrap_z_log_minimal_printk)),
+               "Error: check z_log_minimal_printk function signature");
diff --git a/ports/zephyr/common/memfault_platform_core.c b/ports/zephyr/common/memfault_platform_core.c
index cdd35b3..dcda2f1 100644
--- a/ports/zephyr/common/memfault_platform_core.c
+++ b/ports/zephyr/common/memfault_platform_core.c
@@ -61,7 +61,9 @@
   // register capture disable is a very small size optimization, and logs are
   // likely not used on devices with space constraints.
   LOG_PANIC();
+    #if !defined(CONFIG_LOG_MODE_MINIMAL)
   memfault_zephyr_log_backend_disable();
+    #endif
   #endif
 
   memfault_coredump_cache_fault_regs();
@@ -149,47 +151,6 @@
 }
   #endif  // CONFIG_HWINFO
 
-// This can be overridden by the application to set a custom device ID
-MEMFAULT_WEAK const char *memfault_zephyr_get_device_id(void) {
-  uint8_t dev_id[16] = { 0 };
-  static char dev_id_str[sizeof(dev_id) * 2 + 1];
-  static const char *dev_str = "UNKNOWN";
-
-  // Obtain the device id
-  #if defined(CONFIG_HWINFO)
-  ssize_t length = hwinfo_get_device_id(dev_id, sizeof(dev_id));
-  #else
-  ssize_t length = 0;
-  #endif
-
-  // If hwinfo_get_device_id() fails or isn't enabled, use a fallback string
-  if (length <= 0) {
-  #if defined(CONFIG_SOC)
-    dev_str = CONFIG_SOC "-testserial";
-  #else
-    dev_str = "testserial";
-  #endif
-    length = strlen(dev_str);
-  } else {
-    // Render the obtained serial number in hexadecimal representation
-    for (size_t i = 0; i < length; i++) {
-      (void)snprintf(&dev_id_str[i * 2], sizeof(dev_id_str), "%02x", dev_id[i]);
-    }
-    dev_str = dev_id_str;
-  }
-
-  return dev_str;
-}
-
-MEMFAULT_WEAK void memfault_platform_get_device_info(sMemfaultDeviceInfo *info) {
-  *info = (sMemfaultDeviceInfo){
-    .device_serial = memfault_zephyr_get_device_id(),
-    .software_type = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_TYPE,
-    .software_version = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_VERSION,
-    .hardware_version = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_HARDWARE_VERSION,
-  };
-}
-
 MEMFAULT_WEAK void memfault_reboot_reason_get(sResetBootupInfo *info) {
   eMemfaultRebootReason reset_reason = kMfltRebootReason_Unknown;
   uint32_t reset_reason_reg = 0;
@@ -213,6 +174,47 @@
   memfault_reboot_tracking_collect_reset_info(s_memfault_event_storage);
 }
 
+// This can be overridden by the application to set a custom device ID
+MEMFAULT_WEAK const char *memfault_zephyr_get_device_id(void) {
+  uint8_t dev_id[16] = { 0 };
+  static char dev_id_str[sizeof(dev_id) * 2 + 1];
+  static const char *dev_str = "UNKNOWN";
+
+// Obtain the device id
+#if defined(CONFIG_HWINFO)
+  ssize_t length = hwinfo_get_device_id(dev_id, sizeof(dev_id));
+#else
+  ssize_t length = 0;
+#endif
+
+  // If hwinfo_get_device_id() fails or isn't enabled, use a fallback string
+  if (length <= 0) {
+#if defined(CONFIG_SOC)
+    dev_str = CONFIG_SOC "-testserial";
+#else
+    dev_str = "testserial";
+#endif
+    length = strlen(dev_str);
+  } else {
+    // Render the obtained serial number in hexadecimal representation
+    for (size_t i = 0; i < length; i++) {
+      (void)snprintf(&dev_id_str[i * 2], sizeof(dev_id_str), "%02x", dev_id[i]);
+    }
+    dev_str = dev_id_str;
+  }
+
+  return dev_str;
+}
+
+MEMFAULT_WEAK void memfault_platform_get_device_info(sMemfaultDeviceInfo *info) {
+  *info = (sMemfaultDeviceInfo){
+    .device_serial = memfault_zephyr_get_device_id(),
+    .software_type = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_TYPE,
+    .software_version = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_VERSION,
+    .hardware_version = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_HARDWARE_VERSION,
+  };
+}
+
 // Note: the function signature has changed here across zephyr releases
 // "struct device *dev" -> "const struct device *dev"
 //
diff --git a/scripts/memfault_gdb.py b/scripts/memfault_gdb.py
index 4b0fbb7..0d76ea6 100644
--- a/scripts/memfault_gdb.py
+++ b/scripts/memfault_gdb.py
@@ -286,7 +286,7 @@
 
         # Memory map:
         # https://github.com/espressif/esp-idf/blob/v3.3.1/components/soc/esp32/include/soc/soc.h#L286-L304
-        regions = (
+        return (
             # SOC_DRAM
             (0x3FFAE000, 0x52000),
             # SOC_RTC_DATA
@@ -294,7 +294,6 @@
             # SOC_RTC_DRAM
             (0x3FF80000, 0x2000),
         )
-        return regions
 
     def _read_registers(self, core, gdb_thread, analytics_props):
         # NOTE: The only way I could figure out to read raw registers for CPU1 was
@@ -734,7 +733,7 @@
         self._write(out_f.write, total_size["size"])
 
 
-class Section(object):
+class Section(object):  # noqa: PLW1641
     def __init__(self, addr, size, name, read_only=True):
         self.addr = addr
         self.size = size