Zephyr + ESP32-C3 support, RC1

Based on:

f4c4de30c20ca2511dbff725dd678cb03e6c1bc1
diff --git a/components/demo/src/panics/memfault_demo_panics.c b/components/demo/src/panics/memfault_demo_panics.c
index 7b59880..e2e253f 100644
--- a/components/demo/src/panics/memfault_demo_panics.c
+++ b/components/demo/src/panics/memfault_demo_panics.c
@@ -195,18 +195,6 @@
   return -1;
 }
 
-int memfault_demo_cli_loadaddr(int argc, char *argv[]) {
-  if (argc < 2) {
-    MEMFAULT_LOG_ERROR("Usage: loadaddr <addr>");
-    return -1;
-  }
-  uint32_t addr = (uint32_t)strtoul(argv[1], NULL, 0);
-  uint32_t val = *(uint32_t *)addr;
-
-  MEMFAULT_LOG_INFO("Read 0x%08" PRIx32 " from 0x%08" PRIx32, val, (uint32_t)(uintptr_t)addr);
-  return 0;
-}
-
 #endif  // MEMFAULT_COMPILER_ARM_CORTEX_M
 
 #if MEMFAULT_COMPILER_ARM_V7_A_R
@@ -229,3 +217,15 @@
 }
 
 #endif  // MEMFAULT_COMPILER_ARM_V7_A_R
+
+int memfault_demo_cli_loadaddr(int argc, char *argv[]) {
+  if (argc < 2) {
+    MEMFAULT_LOG_ERROR("Usage: loadaddr <addr>");
+    return -1;
+  }
+  uint32_t addr = (uint32_t)strtoul(argv[1], NULL, 0);
+  uint32_t val = *(uint32_t *)addr;
+
+  MEMFAULT_LOG_INFO("Read 0x%08" PRIx32 " from 0x%08" PRIx32, val, (uint32_t)(uintptr_t)addr);
+  return 0;
+}
diff --git a/components/include/memfault/core/compiler_gcc.h b/components/include/memfault/core/compiler_gcc.h
index aa92ef8..a7be7d5 100644
--- a/components/include/memfault/core/compiler_gcc.h
+++ b/components/include/memfault/core/compiler_gcc.h
@@ -55,6 +55,13 @@
 #  define MEMFAULT_GET_LR(_a) _a = __builtin_return_address(0)
 #  define MEMFAULT_GET_PC(_a) _a = ({ __label__ _l; _l: &&_l;});
 #  define MEMFAULT_BREAKPOINT(val)  __asm__ ("break 0,0")
+#elif defined(__riscv)
+#  define MEMFAULT_GET_LR(_a) _a = __builtin_return_address(0)
+// Take advantage of "Locally Declared Labels" to get a PC
+//   https://gcc.gnu.org/onlinedocs/gcc/Local-Labels.html#Local-Labels
+#  define MEMFAULT_GET_PC(_a) _a = ({ __label__ _l; _l: &&_l;});
+// Trigger a breakpoint exception (similar to bkpt instruction in ARM)
+#define MEMFAULT_BREAKPOINT(val) __asm volatile("ebreak")
 #elif defined(MEMFAULT_UNITTEST) || defined(__APPLE__)  // Memfault iOS SDK also #includes this header
 #  define MEMFAULT_GET_LR(_a) _a = 0
 #  define MEMFAULT_GET_PC(_a) _a = 0
diff --git a/components/include/memfault/demo/cli.h b/components/include/memfault/demo/cli.h
index 0be1b9d..b51d7f0 100644
--- a/components/include/memfault/demo/cli.h
+++ b/components/include/memfault/demo/cli.h
@@ -39,12 +39,12 @@
 //! Command which will generate a UsageFault on Cortex-M hardware
 int memfault_demo_cli_cmd_usagefault(int argc, char *argv[]);
 
+#endif  // MEMFAULT_COMPILER_ARM_CORTEX_M
+
 //! Read a 32-bit memory address and print the value. Can be used to test
 //! specific faults due to protected regions
 int memfault_demo_cli_loadaddr(int argc, char *argv[]);
 
-#endif  // MEMFAULT_COMPILER_ARM_CORTEX_M
-
 #if MEMFAULT_COMPILER_ARM_V7_A_R
 //! Trigger a data abort on an ARMv7-A/R chip
 int memfault_demo_cli_cmd_dataabort(int argc, char *argv[]);
diff --git a/components/panics/src/memfault_fault_handling_riscv.c b/components/panics/src/memfault_fault_handling_riscv.c
index 7dc26ec..065f9f8 100644
--- a/components/panics/src/memfault_fault_handling_riscv.c
+++ b/components/panics/src/memfault_fault_handling_riscv.c
@@ -17,6 +17,7 @@
   #include "memfault/panics/arch/riscv/riscv.h"
   #include "memfault/panics/coredump.h"
   #include "memfault/panics/coredump_impl.h"
+  #include "memfault/panics/fault_handling.h"
 
 const sMfltCoredumpRegion *memfault_coredump_get_arch_regions(size_t *num_regions) {
   *num_regions = 0;
@@ -42,6 +43,66 @@
   prv_fault_handling_assert(pc, lr, reason);
 }
 
+// For non-esp-idf riscv implementations, provide a full assert handler and
+// other utilities.
+  #if defined(__ZEPHYR__) && defined(CONFIG_SOC_FAMILY_ESP32)
+
+    #include "hal/cpu_hal.h"
+
+void memfault_platform_halt_if_debugging(void) {
+  if (cpu_ll_is_debugger_attached()) {
+    MEMFAULT_BREAKPOINT();
+  }
+}
+
+static inline uint32_t prv_read_mstatus(void) {
+  uint32_t mstatus;
+  __asm volatile("csrr %0, mstatus" : "=r"(mstatus));
+  return mstatus;
+}
+
+bool memfault_arch_is_inside_isr(void) {
+  // Read the value of mstatus CSR
+  uint32_t mstatus = prv_read_mstatus();
+
+  // Check the MPIE (Machine Previous Interrupt Enable) bit
+  // If MPIE is set, then the processor is inside an ISR
+  return (mstatus & (1U << 7)) != 0;
+}
+
+static void prv_fault_handling_assert_native(void *pc, void *lr, eMemfaultRebootReason reason) {
+  prv_fault_handling_assert(pc, lr, reason);
+
+  #if MEMFAULT_ASSERT_HALT_IF_DEBUGGING_ENABLED
+  memfault_platform_halt_if_debugging();
+  #endif
+
+  // dereference a null pointer to trigger fault
+  *(uint32_t *)0 = 0x90;
+
+  // We just trap'd into the fault handler logic so it should never be possible to get here but if
+  // we do the best thing that can be done is rebooting the system to recover it.
+  memfault_platform_reboot();
+}
+
+MEMFAULT_NO_OPT
+void memfault_fault_handling_assert_extra(void *pc, void *lr, sMemfaultAssertInfo *extra_info) {
+  prv_fault_handling_assert_native(pc, lr, extra_info->assert_reason);
+
+  MEMFAULT_UNREACHABLE;
+}
+
+MEMFAULT_NO_OPT
+void memfault_fault_handling_assert(void *pc, void *lr) {
+  prv_fault_handling_assert_native(pc, lr, kMfltRebootReason_Assert);
+
+  MEMFAULT_UNREACHABLE;
+}
+
+  #elif !defined(ESP_PLATFORM)
+    #error "Unsupported RISC-V platform, please contact [email protected]"
+  #endif  // !defined(ESP_PLATFORM) && defined(__ZEPHYR__)
+
 void memfault_fault_handler(const sMfltRegState *regs, eMemfaultRebootReason reason) {
   if (s_crash_reason == kMfltRebootReason_Unknown) {
     // TODO confirm this works correctly- we should have the correct
diff --git a/examples/esp32/apps/memfault_demo_app/main/Kconfig.projbuild b/examples/esp32/apps/memfault_demo_app/main/Kconfig.projbuild
index c05b5a9..e134029 100644
--- a/examples/esp32/apps/memfault_demo_app/main/Kconfig.projbuild
+++ b/examples/esp32/apps/memfault_demo_app/main/Kconfig.projbuild
@@ -2,11 +2,12 @@
 
 config STORE_HISTORY
     bool "Store command history in flash"
-    default y
+    default n
     help
         Linenoise line editing library provides functions to save and load
         command history. If this option is enabled, initalizes a FAT filesystem
         and uses it to store command history.
+        Note that this adds a ~22kB heap allocation on system boot.
 
 config MEMFAULT_APP_OTA
     bool "Enable automatic periodic check+update for OTA"
diff --git a/examples/esp32/apps/memfault_demo_app/main/cmd_app.c b/examples/esp32/apps/memfault_demo_app/main/cmd_app.c
index 2ecc23f..d5e01a4 100644
--- a/examples/esp32/apps/memfault_demo_app/main/cmd_app.c
+++ b/examples/esp32/apps/memfault_demo_app/main/cmd_app.c
@@ -11,6 +11,7 @@
 
 #include "cmd_decl.h"
 #include "esp_console.h"
+#include "esp_heap_task_info.h"
 #include "esp_log.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/semphr.h"
@@ -110,6 +111,47 @@
           // !defined(CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE)
 #endif    // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
 
+#if defined(CONFIG_HEAP_TASK_TRACKING)
+// Print out per-task heap allocations. This is lifted from the example here:
+// https://github.com/espressif/esp-idf/blob/v5.1.2/examples/system/heap_task_tracking/main/heap_task_tracking_main.c
+static int prv_heap_task_stats(MEMFAULT_UNUSED int argc, MEMFAULT_UNUSED char **argv) {
+  #define MAX_TASK_NUM 10   // Max number of per tasks info that it can store
+  #define MAX_BLOCK_NUM 10  // Max number of per block info that it can store
+
+  static size_t s_prepopulated_num = 0;
+  static heap_task_totals_t s_totals_arr[MAX_TASK_NUM];
+  static heap_task_block_t s_block_arr[MAX_BLOCK_NUM];
+
+  heap_task_info_params_t heap_info = {0};
+  heap_info.caps[0] = MALLOC_CAP_8BIT;  // Gets heap with CAP_8BIT capabilities
+  heap_info.mask[0] = MALLOC_CAP_8BIT;
+  heap_info.caps[1] = MALLOC_CAP_32BIT;  // Gets heap info with CAP_32BIT capabilities
+  heap_info.mask[1] = MALLOC_CAP_32BIT;
+  heap_info.tasks = NULL;  // Passing NULL captures heap info for all tasks
+  heap_info.num_tasks = 0;
+  heap_info.totals = s_totals_arr;  // Gets task wise allocation details
+  heap_info.num_totals = &s_prepopulated_num;
+  heap_info.max_totals = MAX_TASK_NUM;  // Maximum length of "s_totals_arr"
+  heap_info.blocks = s_block_arr;  // Gets block wise allocation details. For each block, gets owner
+                                   // task, address and size
+  heap_info.max_blocks = MAX_BLOCK_NUM;  // Maximum length of "s_block_arr"
+
+  heap_caps_get_per_task_info(&heap_info);
+
+  for (int i = 0; i < *heap_info.num_totals; i++) {
+    printf(
+      "Task: %s -> CAP_8BIT: %d CAP_32BIT: %d\n",
+      heap_info.totals[i].task ? pcTaskGetName(heap_info.totals[i].task) : "Pre-Scheduler allocs",
+      heap_info.totals[i].size[0],   // Heap size with CAP_8BIT capabilities
+      heap_info.totals[i].size[1]);  // Heap size with CAP32_BIT capabilities
+  }
+
+  printf("\n\n");
+
+  return 0;
+}
+#endif  // CONFIG_HEAP_TASK_TRACKING
+
 void register_app(void) {
 #if MEMFAULT_TASK_WATCHDOG_ENABLE
   const esp_console_cmd_t test_watchdog_cmd = {
@@ -121,6 +163,16 @@
   ESP_ERROR_CHECK(esp_console_cmd_register(&test_watchdog_cmd));
 #endif
 
+#if defined(CONFIG_HEAP_TASK_TRACKING)
+  const esp_console_cmd_t heap_task_stats_cmd = {
+    .command = "heap_task_stats",
+    .help = "Prints heap usage per task",
+    .hint = NULL,
+    .func = &prv_heap_task_stats,
+  };
+  ESP_ERROR_CHECK(esp_console_cmd_register(&heap_task_stats_cmd));
+#endif
+
 // Only support the stack overflow test on esp-idf >= 4.3.0
 #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
   #if defined(CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK) || \
diff --git a/examples/esp32/apps/memfault_demo_app/main/console_example_main.c b/examples/esp32/apps/memfault_demo_app/main/console_example_main.c
index 9ab2416..0cdc351 100644
--- a/examples/esp32/apps/memfault_demo_app/main/console_example_main.c
+++ b/examples/esp32/apps/memfault_demo_app/main/console_example_main.c
@@ -35,7 +35,10 @@
 #include "nvs_flash.h"
 #include "settings.h"
 
-static const char *TAG = "example";
+// Conditionally enable the logging tag variable only when it's used
+#if defined(CONFIG_STORE_HISTORY) || defined(CONFIG_HEAP_USE_HOOKS)
+static const char *TAG = "main";
+#endif
 
 /* Console command history can be stored to and loaded from a file.
  * The easiest way to do this is to use FATFS filesystem on top of
@@ -121,7 +124,7 @@
   linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint);
 
   /* Set command history size */
-  linenoiseHistorySetMaxLen(100);
+  linenoiseHistorySetMaxLen(10);
 
 #if CONFIG_STORE_HISTORY
   /* Load command history from filesystem */
@@ -333,11 +336,11 @@
   // In our app, there's a periodic 1696 byte alloc. Filter out anything that
   // size or smaller from this log, otherwise it's quite spammy
   if (size > 1696) {
-    ESP_LOGI(TAG, "Large alloc: %p, size: %d, caps: %lu", ptr, size, caps);
+    ESP_LOGI("main", "Large alloc: %p, size: %d, caps: %lu", ptr, size, caps);
 
     multi_heap_info_t heap_info = {0};
     heap_caps_get_info(&heap_info, MALLOC_CAP_DEFAULT);
-    ESP_LOGI(TAG, "Total free bytes: %d", heap_info.total_free_bytes);
+    ESP_LOGI("main", "Total free bytes: %d", heap_info.total_free_bytes);
   }
 }
 #endif
diff --git a/examples/esp32/apps/memfault_demo_app/sdkconfig.defaults b/examples/esp32/apps/memfault_demo_app/sdkconfig.defaults
index 582898c..495fa7d 100644
--- a/examples/esp32/apps/memfault_demo_app/sdkconfig.defaults
+++ b/examples/esp32/apps/memfault_demo_app/sdkconfig.defaults
@@ -7,6 +7,9 @@
 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3072
 CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
 
+# Enable watchpoint stack overflow guard
+CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
+
 # Enable filesystem
 CONFIG_PARTITION_TABLE_CUSTOM=y
 CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
diff --git a/examples/esp32/apps/memfault_demo_app/sdkconfig.heaptrace b/examples/esp32/apps/memfault_demo_app/sdkconfig.heaptrace
new file mode 100644
index 0000000..8c2445b
--- /dev/null
+++ b/examples/esp32/apps/memfault_demo_app/sdkconfig.heaptrace
@@ -0,0 +1,4 @@
+# Heap tracing configs
+CONFIG_HEAP_POISONING_LIGHT=y
+CONFIG_HEAP_TASK_TRACKING=y
+CONFIG_HEAP_USE_HOOKS=y
diff --git a/examples/zephyr/qemu/qemu-app/config/memfault_platform_config.h b/examples/zephyr/qemu/qemu-app/config/memfault_platform_config.h
new file mode 100644
index 0000000..32b508a
--- /dev/null
+++ b/examples/zephyr/qemu/qemu-app/config/memfault_platform_config.h
@@ -0,0 +1,20 @@
+#pragma once
+
+//! @file
+//!
+//! @brief
+//! Platform overrides for the default configuration settings in the memfault-firmware-sdk.
+//! Default configuration settings can be found in "memfault/config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(CONFIG_SOC_FAMILY_ESP32)
+  #define ZEPHYR_DATA_REGION_START _data_start
+  #define ZEPHYR_DATA_REGION_END _data_end
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/examples/zephyr/qemu/qemu-app/src/main.c b/examples/zephyr/qemu/qemu-app/src/main.c
index 67a0884..0c5cb82 100644
--- a/examples/zephyr/qemu/qemu-app/src/main.c
+++ b/examples/zephyr/qemu/qemu-app/src/main.c
@@ -38,9 +38,7 @@
 
 // Blink code taken from the zephyr/samples/basic/blinky example.
 static void blink_forever(void) {
-#if CONFIG_QEMU_TARGET
-  k_sleep(K_FOREVER);
-#else
+#if DT_NODE_HAS_PROP(DT_ALIAS(led0), gpios)
   /* 1000 msec = 1 sec */
   #define SLEEP_TIME_MS 1000
 
@@ -58,7 +56,10 @@
     gpio_pin_toggle_dt(&led);
     k_msleep(SLEEP_TIME_MS);
   }
-#endif  // CONFIG_QEMU_TARGET
+#else
+  // no led on this board, just sleep forever
+  k_sleep(K_FOREVER);
+#endif
 }
 
 void memfault_platform_get_device_info(sMemfaultDeviceInfo *info) {
diff --git a/examples/zephyr/qemu/qemu-app/west.yml b/examples/zephyr/qemu/qemu-app/west.yml
index 32fe7d0..5a135eb 100644
--- a/examples/zephyr/qemu/qemu-app/west.yml
+++ b/examples/zephyr/qemu/qemu-app/west.yml
@@ -18,6 +18,7 @@
         # Limit the Zephyr modules to the required set
         name-allowlist:
             - cmsis
+            - hal_espressif
 
     - name: memfault-firmware-sdk
       path: modules/lib/memfault-firmware-sdk
diff --git a/ports/esp_idf/memfault/Kconfig b/ports/esp_idf/memfault/Kconfig
index 6b00b28..dbeff8b 100644
--- a/ports/esp_idf/memfault/Kconfig
+++ b/ports/esp_idf/memfault/Kconfig
@@ -150,8 +150,12 @@
     config MEMFAULT_ASSERT_ON_ALLOC_FAILURE
         bool "Assert on allocation failure"
         default n
+        depends on !HEAP_ABORT_WHEN_ALLOCATION_FAILS
         help
             When enabled, the Memfault SDK will assert if any allocation fails.
-            This can be useful for tracking down heap memory issues.
+            This can be useful for tracking down heap memory issues. Note that
+            this operates similarly to HEAP_ABORT_WHEN_ALLOCATION_FAILS, but the
+            Memfault Issue created will be tagged as "Out of Memory" instead of
+            a generic "Assert".
 
 endmenu
diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt
index b0d5405..139ccd9 100644
--- a/ports/zephyr/CMakeLists.txt
+++ b/ports/zephyr/CMakeLists.txt
@@ -18,9 +18,15 @@
     list(APPEND MEMFAULT_COMPONENTS metrics)
   endif()
 
+  if(CONFIG_RISCV)
+    set(MEMFAULT_ARCH "ARCH_RISCV")
+  else()
+    set(MEMFAULT_ARCH "ARCH_ARM_CORTEX_M")
+  endif()
+
   include(${MEMFAULT_SDK_ROOT}/cmake/Memfault.cmake)
   memfault_library(${MEMFAULT_SDK_ROOT} MEMFAULT_COMPONENTS
-    MEMFAULT_COMPONENTS_SRCS MEMFAULT_COMPONENTS_INC_FOLDERS)
+    MEMFAULT_COMPONENTS_SRCS MEMFAULT_COMPONENTS_INC_FOLDERS ${MEMFAULT_ARCH})
 
   # Add Memfault SDK sources to memfault library
   zephyr_interface_library_named(memfault)
@@ -70,4 +76,30 @@
     zephyr_include_directories(${ZEPHYR_BASE}/include/zephyr)
   endif()
 
+  # If enabled, apply a post-build step to generate a Memfault build id
+  if(CONFIG_MEMFAULT_USE_MEMFAULT_BUILD_ID)
+    get_filename_component(
+      memfault_fw_build_id_script
+      ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/fw_build_id.py
+      ABSOLUTE
+    )
+    # force our command to be at the front of the list so it runs before any
+    # other operations on the .elf file, including binary/OTA image generation
+    get_property(
+      PROPERTY_EXTRA_POST_BUILD_COMMANDS
+      GLOBAL
+      PROPERTY extra_post_build_commands
+    )
+    set_property(
+      GLOBAL
+      PROPERTY extra_post_build_commands
+               COMMAND
+               ${PYTHON_EXECUTABLE}
+               ${memfault_fw_build_id_script}
+               ${CMAKE_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.elf
+               # now append the rest of the commands
+               ${PROPERTY_EXTRA_POST_BUILD_COMMANDS}
+    )
+  endif()
+
 endif()
diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig
index 325ec1c..2673e83 100644
--- a/ports/zephyr/Kconfig
+++ b/ports/zephyr/Kconfig
@@ -1,9 +1,9 @@
 config MEMFAULT
         bool "Memfault Support"
         default n
-        depends on CPU_CORTEX_M
+        depends on CPU_CORTEX_M || RISCV
         select RUNTIME_NMI
-        select EXTRA_EXCEPTION_INFO
+        select EXTRA_EXCEPTION_INFO if CPU_CORTEX_M
         select DEBUG_THREAD_INFO
         help
           Enable Zephyr Integration with the Memfault SDK
@@ -463,8 +463,21 @@
          memfault_zephyr_date_time_evt_handler() function should be called from
          the application's date_time event handler.
 
+config MEMFAULT_USE_MEMFAULT_BUILD_ID
+        bool "Use a Memfault build ID instead of a GNU build ID"
+        default y if SOC_FAMILY_ESP32
+        depends on !MEMFAULT_USE_GNU_BUILD_ID
+        help
+          Use a Memfault build ID generated by scripts/fw_build_id.py instead
+          of a GNU build id. This is useful if the board is being flashed with
+          the esptool.py script, which doesn't correctly load the
+          .note.gnu.build-id section.
+
 menuconfig MEMFAULT_USE_GNU_BUILD_ID
-        default y
+        # esptool.py, the flashing tool for esp32 devices, does not flash the
+        # .note.gnu.build-id section correctly. only enable by default for
+        # non-esp32 devices
+        default y if !SOC_FAMILY_ESP32
         bool "Use a GNU build ID in an image"
 
 if MEMFAULT_USE_GNU_BUILD_ID
diff --git a/ports/zephyr/common/memfault-build-id.ld b/ports/zephyr/common/memfault-build-id.ld
index 6490730..ecb3af6 100644
--- a/ports/zephyr/common/memfault-build-id.ld
+++ b/ports/zephyr/common/memfault-build-id.ld
@@ -2,4 +2,4 @@
 {
   __start_gnu_build_id_start = .;
   KEEP(*(.note.gnu.build-id))
-} GROUP_LINK_IN(ROMABLE_REGION)
+} GROUP_ROM_LINK_IN(RODATA_REGION, ROMABLE_REGION)
diff --git a/ports/zephyr/common/memfault_demo_cli.c b/ports/zephyr/common/memfault_demo_cli.c
index 4c260cb..d875e96 100644
--- a/ports/zephyr/common/memfault_demo_cli.c
+++ b/ports/zephyr/common/memfault_demo_cli.c
@@ -188,6 +188,8 @@
   return -1;
 }
 
+#if CONFIG_ARM
+
 static int prv_busfault_example(const struct shell *shell, size_t argc, char **argv) {
   //! Note: The Zephyr fault handler dereferences the pc which triggers a fault
   //! if the pc itself is from a bad pointer:
@@ -217,6 +219,8 @@
   return -1;
 }
 
+#endif  // CONFIG_ARM
+
 static int prv_zephyr_assert_example(const struct shell *shell, size_t argc, char **argv) {
 #if !CONFIG_ASSERT
   shell_print(shell, "CONFIG_ASSERT was disabled in the build, this command will have no effect");
@@ -273,13 +277,17 @@
 SHELL_STATIC_SUBCMD_SET_CREATE(
   sub_memfault_crash_cmds,
   //! different crash types that should result in a coredump being collected
-  SHELL_CMD(assert, NULL, "trigger memfault assert", prv_memfault_assert_example),
+
+  #if CONFIG_ARM
   SHELL_CMD(busfault, NULL, "trigger a busfault", prv_busfault_example),
-  SHELL_CMD(hang, NULL, "trigger a hang", prv_hang_example),
   SHELL_CMD(hardfault, NULL, "trigger a hardfault", prv_hardfault_example),
   SHELL_CMD(memmanage, NULL, "trigger a memory management fault", prv_memmanage_example),
   SHELL_CMD(usagefault, NULL, "trigger a usage fault", prv_usagefault_example),
+  #endif  // CONFIG_ARM
+
+  SHELL_CMD(hang, NULL, "trigger a hang", prv_hang_example),
   SHELL_CMD(zassert, NULL, "trigger a zephyr assert", prv_zephyr_assert_example),
+  SHELL_CMD(assert, NULL, "trigger memfault assert", prv_memfault_assert_example),
   SHELL_CMD(loadaddr, NULL, "test a 32 bit load from an address", prv_zephyr_load_32bit_address),
   SHELL_CMD(double_free, NULL, "trigger a double free error", prv_cli_cmd_double_free),
   SHELL_CMD(badptr, NULL, "trigger fault via store to a bad address", prv_bad_ptr_deref_example),
diff --git a/ports/zephyr/common/memfault_platform_coredump_regions.c b/ports/zephyr/common/memfault_platform_coredump_regions.c
index b1a64eb..503977e 100644
--- a/ports/zephyr/common/memfault_platform_coredump_regions.c
+++ b/ports/zephyr/common/memfault_platform_coredump_regions.c
@@ -15,6 +15,7 @@
 #include "memfault/panics/platform/coredump.h"
 #include "memfault/ports/zephyr/version.h"
 
+#if CONFIG_ARM
 #if MEMFAULT_ZEPHYR_VERSION_GT(3, 4)
   #include <cmsis_core.h>
 #elif MEMFAULT_ZEPHYR_VERSION_GT(2, 1)
@@ -22,6 +23,7 @@
 #else
   #include MEMFAULT_ZEPHYR_INCLUDE(arch/arm/cortex_m/cmsis.h)
 #endif
+#endif
 
 #include "memfault/core/compiler.h"
 #include "memfault/core/math.h"
@@ -38,8 +40,6 @@
   size_t region_idx = 0;
 
 #if CONFIG_MEMFAULT_COREDUMP_COLLECT_STACK_REGIONS
-  const bool msp_was_active = (crash_info->exception_reg_state->exc_return & (1 << 2)) == 0;
-
   size_t stack_size_to_collect = memfault_platform_sanitize_address_range(
     crash_info->stack_address, CONFIG_MEMFAULT_COREDUMP_STACK_SIZE_TO_COLLECT);
 
@@ -47,6 +47,9 @@
     MEMFAULT_COREDUMP_MEMORY_REGION_INIT(crash_info->stack_address, stack_size_to_collect);
   region_idx++;
 
+  #if CONFIG_ARM
+  const bool msp_was_active = (crash_info->exception_reg_state->exc_return & (1 << 2)) == 0;
+
   if (msp_was_active) {
     // System crashed in an ISR but the running task state is on PSP so grab that too
     void *psp = (void *)(uintptr_t)__get_PSP();
@@ -59,6 +62,7 @@
     regions[region_idx] = MEMFAULT_COREDUMP_MEMORY_REGION_INIT(psp, stack_size_to_collect);
     region_idx++;
   }
+  #endif  // CONFIG_ARM
 #endif
 
 #if CONFIG_MEMFAULT_COREDUMP_COLLECT_KERNEL_REGION
diff --git a/ports/zephyr/common/memfault_zephyr_ram_regions.c b/ports/zephyr/common/memfault_zephyr_ram_regions.c
index c8a35c4..b340c5b 100644
--- a/ports/zephyr/common/memfault_zephyr_ram_regions.c
+++ b/ports/zephyr/common/memfault_zephyr_ram_regions.c
@@ -93,6 +93,11 @@
 
 MEMFAULT_WEAK
 size_t memfault_platform_sanitize_address_range(void *start_addr, size_t desired_size) {
+  #if CONFIG_RISCV
+  // Linker script does not define _image_ram_start/end for RISC-V
+  const uint32_t ram_start = 0;
+  const uint32_t ram_end = 0xffffffff;
+  #else
   // NB: This only works for MCUs which have a contiguous RAM address range. (i.e Any MCU in the
   // nRF53, nRF52, and nRF91 family). All of these MCUs have a contigous RAM address range so it is
   // sufficient to just look at _image_ram_start/end from the Zephyr linker script
@@ -101,6 +106,7 @@
 
   const uint32_t ram_start = (uint32_t)_image_ram_start;
   const uint32_t ram_end = (uint32_t)_image_ram_end;
+  #endif
 
   if ((uint32_t)start_addr >= ram_start && (uint32_t)start_addr < ram_end) {
     return MEMFAULT_MIN(desired_size, ram_end - (uint32_t)start_addr);
@@ -199,7 +205,13 @@
     }
 #endif
 
-    void *sp = (void *)thread->callee_saved.psp;
+    void *sp =
+  #if CONFIG_ARM
+    (void *)thread->callee_saved.psp
+  #else
+    (void *)thread->callee_saved.sp
+  #endif
+  ;
 
 #if defined(CONFIG_THREAD_STACK_INFO)
     // We know where the top of the stack is. Use that information to shrink
@@ -259,13 +271,15 @@
   // with a Memfault SDK version >=0.27.3, because that NCS release used an
   // intermediate Zephyr release, so the version number checking is not
   // possible.
-#if !MEMFAULT_ZEPHYR_USE_OLD_DATA_REGION_NAMES && MEMFAULT_ZEPHYR_VERSION_GT(2, 6)
-#define ZEPHYR_DATA_REGION_START __data_region_start
-#define ZEPHYR_DATA_REGION_END __data_region_end
-#else
-  // The old names are used in previous Zephyr versions (<=2.6)
-#define ZEPHYR_DATA_REGION_START __data_ram_start
-#define ZEPHYR_DATA_REGION_END __data_ram_end
+#if !defined(ZEPHYR_DATA_REGION_START) && !defined(ZEPHYR_DATA_REGION_END)
+  #if !MEMFAULT_ZEPHYR_USE_OLD_DATA_REGION_NAMES && MEMFAULT_ZEPHYR_VERSION_GT(2, 6)
+    #define ZEPHYR_DATA_REGION_START __data_region_start
+    #define ZEPHYR_DATA_REGION_END __data_region_end
+  #else
+    // The old names are used in previous Zephyr versions (<=2.6)
+    #define ZEPHYR_DATA_REGION_START __data_ram_start
+    #define ZEPHYR_DATA_REGION_END __data_ram_end
+  #endif
 #endif
 
   extern uint32_t ZEPHYR_DATA_REGION_START[];
diff --git a/ports/zephyr/v2.4/CMakeLists.txt b/ports/zephyr/v2.4/CMakeLists.txt
index 0e428b2..263e0f6 100644
--- a/ports/zephyr/v2.4/CMakeLists.txt
+++ b/ports/zephyr/v2.4/CMakeLists.txt
@@ -1,4 +1,12 @@
-zephyr_library_sources(memfault_fault_handler.c)
+if (CONFIG_RISCV)
+    zephyr_library_sources(memfault_fault_handler_riscv.c)
+elseif(CONFIG_ARM)
+    zephyr_library_sources(memfault_fault_handler.c)
+else()
+    # Unsupported configuration
+    message(FATAL_ERROR "Unsupported chip architecture")
+endif()
+
 zephyr_include_directories(.)
 
 # Zephyr fatals for ARM Cortex-M's take the following path:
diff --git a/ports/zephyr/v2.4/memfault_fault_handler_riscv.c b/ports/zephyr/v2.4/memfault_fault_handler_riscv.c
new file mode 100644
index 0000000..0218163
--- /dev/null
+++ b/ports/zephyr/v2.4/memfault_fault_handler_riscv.c
@@ -0,0 +1,84 @@
+//! @file
+//!
+//!
+
+#include <arch/cpu.h>
+#include <fatal.h>
+#include <init.h>
+#include <logging/log.h>
+#include <logging/log_ctrl.h>
+#include <soc.h>
+
+#include "memfault/core/compiler.h"
+#include "memfault/core/platform/core.h"
+#include "memfault/core/reboot_reason_types.h"
+#include "memfault/panics/arch/riscv/riscv.h"
+#include "memfault/panics/coredump.h"
+#include "memfault/panics/fault_handling.h"
+#include "memfault/ports/zephyr/version.h"
+
+// Note: There is no header exposed for this zephyr function
+extern void sys_arch_reboot(int type);
+
+// Intercept zephyr/kernel/fatal.c:z_fatal_error()
+void __wrap_z_fatal_error(unsigned int reason, const z_arch_esf_t *esf);
+
+void __wrap_z_fatal_error(unsigned int reason, const z_arch_esf_t *esf) {
+  // flush logs prior to capturing coredump & rebooting
+  LOG_PANIC();
+
+  sMfltRegState reg = {
+    .mepc = esf->mepc, /* machine exception program counter */
+    .ra = esf->ra,
+#ifdef CONFIG_USERSPACE
+    .sp = esf->sp, /* preserved (user or kernel) stack pointer */
+#endif
+    // .gp = ?,
+    // .tp = ?,
+    .t =
+      {
+        esf->t0, /* Caller-saved temporary register */
+        esf->t1, /* Caller-saved temporary register */
+        esf->t2, /* Caller-saved temporary register */
+#if !defined(CONFIG_RISCV_ISA_RV32E)
+        esf->t3, /* Caller-saved temporary register */
+        esf->t4, /* Caller-saved temporary register */
+        esf->t5, /* Caller-saved temporary register */
+        esf->t6, /* Caller-saved temporary register */
+#endif
+      },
+    .s =
+      {
+        esf->s0, /* callee-saved s0 */
+      },
+    .a =
+      {
+        esf->a0, /* function argument/return value */
+        esf->a1, /* function argument */
+        esf->a2, /* function argument */
+        esf->a3, /* function argument */
+        esf->a4, /* function argument */
+        esf->a5, /* function argument */
+#if !defined(CONFIG_RISCV_ISA_RV32E)
+        esf->a6, /* function argument */
+        esf->a7, /* function argument */
+#endif
+      },
+    .mstatus = esf->mstatus,
+    // .mtvec = ?,
+    // .mcause = ?,
+    // .mtval = ?,
+    // .mhartid = ?,
+  };
+
+  memfault_fault_handler(&reg, kMfltRebootReason_HardFault);
+}
+
+MEMFAULT_WEAK
+MEMFAULT_NORETURN
+void memfault_platform_reboot(void) {
+  memfault_platform_halt_if_debugging();
+
+  sys_arch_reboot(0);
+  CODE_UNREACHABLE;
+}
diff --git a/scripts/memfault_gdb.py b/scripts/memfault_gdb.py
index 7ff249f..e12ddef 100644
--- a/scripts/memfault_gdb.py
+++ b/scripts/memfault_gdb.py
@@ -1350,7 +1350,7 @@
     """Captures a coredump from the target and uploads it to Memfault for analysis"""
 
     ALPHANUM_SLUG_DOTS_COLON_REGEX = r"^[-a-zA-Z0-9_\.\+:]+$"
-    ALPHANUM_SLUG_DOTS_COLON_SPACES_PARENS_SLASH_REGEX = r"^[-a-zA-Z0-9_\.\+: \(\)\[\]/]+$"
+    ALPHANUM_SLUG_DOTS_COLON_SPACES_PARENS_SLASH_COMMA_REGEX = r"^[-a-zA-Z0-9_\.\+: \(\)\[\]/,]+$"
     DEFAULT_CORE_DUMP_HARDWARE_REVISION = "DEVBOARD"
     DEFAULT_CORE_DUMP_SERIAL_NUMBER = "DEMOSERIALNUMBER"
     DEFAULT_CORE_DUMP_SOFTWARE_TYPE = "main"
@@ -1541,7 +1541,7 @@
         parser.add_argument(
             "--software-version",
             type=_character_check(
-                self.ALPHANUM_SLUG_DOTS_COLON_SPACES_PARENS_SLASH_REGEX, "software version"
+                self.ALPHANUM_SLUG_DOTS_COLON_SPACES_PARENS_SLASH_COMMA_REGEX, "software version"
             ),
             help=(
                 "Overrides the software version that will be reported in the core dump."