pw_boot_armv7m: add pw_boot_PostMain()

Adds a hook which is invoked after main() returns which is
provided by the user and must never return.

This is immediately used by lm3s6965evb-qemu to remove QEMU
specific details out of pw_boot_armv7m.

Change-Id: If4c68411c7158354ac0d7cd472f959cbcdfcd13b
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/16701
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_boot_armv7m/BUILD.gn b/pw_boot_armv7m/BUILD.gn
index 2e3c6ff..85fb257 100644
--- a/pw_boot_armv7m/BUILD.gn
+++ b/pw_boot_armv7m/BUILD.gn
@@ -22,10 +22,6 @@
   # TODO(frolv): Move this into pw_boot module when it is created.
   pw_boot_BACKEND = ""
 
-  # Whether or not to include code that signals to QEMU to shut down the
-  # emulator. This should only be enabled on QEMU targets.
-  pw_boot_armv7m_QEMU_SHUTDOWN = false
-
   # This list should contain the necessary defines for setting pw_boot linker
   # script memory regions.
   pw_boot_armv7m_LINK_CONFIG_DEFINES = []
@@ -49,9 +45,6 @@
       ":armv7m_linker_script",
       "$dir_pw_preprocessor",
     ]
-    if (pw_boot_armv7m_QEMU_SHUTDOWN) {
-      defines = [ "PW_BOOT_ARMV7M_QEMU_SHUTDOWN=1" ]
-    }
     public = [ "public/pw_boot_armv7m/boot.h" ]
     sources = [ "core_init.c" ]
   }
diff --git a/pw_boot_armv7m/core_init.c b/pw_boot_armv7m/core_init.c
index 49200a8..c1b7307 100644
--- a/pw_boot_armv7m/core_init.c
+++ b/pw_boot_armv7m/core_init.c
@@ -49,6 +49,7 @@
 //     3.4. Static C++ constructors
 //     3.5. pw_boot_PreMainInit()
 //     3.6. main()
+//     3.7. pw_boot_PostMain()
 
 #include <stdbool.h>
 #include <stdint.h>
@@ -119,13 +120,8 @@
   // Run main.
   main();
 
-#if PW_BOOT_ARMV7M_QEMU_SHUTDOWN
-  // QEMU requires a special command to tell the VM to shut down.
-  volatile uint32_t* aircr = (uint32_t*)(0xE000ED0CU);
-  *aircr = 0x5fa0004;
-#endif  // PW_BOOT_ARMV7M_QEMU_SHUTDOWN
+  // In case main() returns, invoke this hook.
+  pw_boot_PostMain();
 
-  // In case main() returns, just sit here until the device is reset.
-  while (true) {
-  }
+  PW_UNREACHABLE;
 }
diff --git a/pw_boot_armv7m/docs.rst b/pw_boot_armv7m/docs.rst
index 7696a9b..74bdd86 100644
--- a/pw_boot_armv7m/docs.rst
+++ b/pw_boot_armv7m/docs.rst
@@ -41,6 +41,8 @@
     // C++ static constructors are invoked.
     pw_boot_PreMainInit();  // User-implemented function.
     main();  // User-implemented function.
+    pw_boot_PostMain();  // User-implemented function.
+    PW_UNREACHABLE;
   }
 
 Setup
@@ -48,7 +50,8 @@
 
 User-Implemented Functions
 --------------------------
-This module expects three extern "C" functions to be defined outside this module.
+This module expects all of these extern "C" functions to be defined outside this
+module:
 
  - ``int main()``: This is where applications reside.
  - ``void pw_boot_PreStaticMemoryInit()``: This function executes just before
@@ -89,6 +92,13 @@
    Depending on your platform, this might be turning on a UART, setting up
    default clocks, etc.
 
+ - ``PW_NO_RETURN void pw_boot_PostMain()``: This function executes after main
+   has returned. This could be used for device specific teardown such as an
+   infinite loop, soft reset, or QEMU shutdown. In addition, if relevant for
+   your application, this would be the place to invoke the global static
+   destructors. This function must not return!
+
+
 If any of these functions are unimplemented, executables will encounter a link
 error.
 
diff --git a/pw_boot_armv7m/public/pw_boot_armv7m/boot.h b/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
index dfe990f..9b683d5 100644
--- a/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
+++ b/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
@@ -95,7 +95,8 @@
 // .data section in an ELF file).
 // WARNING: Be EXTREMELY careful when in the context of this function as it
 // violates the C spec in several ways as .bss has not yet been zero-initialized
-// and static values have not yet been loaded into memory.
+// and static values have not yet been loaded into memory. This function is NOT
+// implemented by pw_boot_armv7m.
 void pw_boot_PreStaticMemoryInit();
 
 // pw_boot hook: Before C++ static constructors are invoked (user supplied).
@@ -104,7 +105,8 @@
 // after zero initialization of RAM and loading values into static memory
 // (commonly labeled as the .data section in an ELF file). Per the naming, this
 // function is called just before C++ static constructors are invoked. It is
-// safe to run C code, but NOT safe to call out to any C++ code.
+// safe to run C code, but NOT safe to call out to any C++ code. This function
+// is NOT implemented by pw_boot_armv7m.
 void pw_boot_PreStaticConstructorInit();
 
 // pw_boot hook: Before main is invoked (user supplied).
@@ -116,4 +118,11 @@
 // pw_boot_armv7m.
 void pw_boot_PreMainInit();
 
+// pw_boot hook: After main returned (user supplied).
+//
+// This is a hook function that users of pw_boot must supply. It is called by
+// pw_boot_Entry() after main() has returned. This function must not return!
+// This function is NOT implemented by pw_boot_armv7m.
+PW_NO_RETURN void pw_boot_PostMain();
+
 PW_EXTERN_C_END
diff --git a/pw_preprocessor/public/pw_preprocessor/compiler.h b/pw_preprocessor/public/pw_preprocessor/compiler.h
index 550109e..8f87dbd 100644
--- a/pw_preprocessor/public/pw_preprocessor/compiler.h
+++ b/pw_preprocessor/public/pw_preprocessor/compiler.h
@@ -70,7 +70,7 @@
 
 // Indicate to the compiler that the annotated function won't return. Example:
 //
-//   void HandleAssertFailure(ErrorCode error_code) PW_NO_RETURN;
+//   PW_NO_RETURN void HandleAssertFailure(ErrorCode error_code);
 //
 #define PW_NO_RETURN __attribute__((noreturn))
 
diff --git a/targets/lm3s6965evb-qemu/early_boot.c b/targets/lm3s6965evb-qemu/early_boot.c
index 27bf869..0d1922f 100644
--- a/targets/lm3s6965evb-qemu/early_boot.c
+++ b/targets/lm3s6965evb-qemu/early_boot.c
@@ -13,6 +13,7 @@
 // the License.
 
 #include "pw_boot_armv7m/boot.h"
+#include "pw_preprocessor/compiler.h"
 #include "pw_sys_io_baremetal_lm3s6965evb/init.h"
 
 void pw_boot_PreStaticMemoryInit() {
@@ -28,3 +29,14 @@
 void pw_boot_PreStaticConstructorInit() {}
 
 void pw_boot_PreMainInit() { pw_sys_io_Init(); }
+
+PW_NO_RETURN void pw_boot_PostMain() {
+  // QEMU requires a special command to tell the VM to shut down.
+  volatile uint32_t* aircr = (uint32_t*)(0xE000ED0CU);
+  *aircr = 0x5fa0004;
+
+  // In case main() returns, just sit here until the device is reset.
+  while (1) {
+  }
+  PW_UNREACHABLE;
+}
diff --git a/targets/lm3s6965evb-qemu/target_toolchains.gni b/targets/lm3s6965evb-qemu/target_toolchains.gni
index bd4044b..d2d2d02 100644
--- a/targets/lm3s6965evb-qemu/target_toolchains.gni
+++ b/targets/lm3s6965evb-qemu/target_toolchains.gni
@@ -33,9 +33,6 @@
 
   pw_unit_test_AUTOMATIC_RUNNER = get_path_info(_test_runner_script, "abspath")
 
-  # Tell QEMU to shut down after running a binary.
-  pw_boot_armv7m_QEMU_SHUTDOWN = true
-
   # Facade backends
   pw_assert_BACKEND = dir_pw_assert_basic
   pw_boot_BACKEND = dir_pw_boot_armv7m
diff --git a/targets/stm32f429i-disc1/early_boot.c b/targets/stm32f429i-disc1/early_boot.c
index 0e07163..e372eaa 100644
--- a/targets/stm32f429i-disc1/early_boot.c
+++ b/targets/stm32f429i-disc1/early_boot.c
@@ -14,6 +14,7 @@
 
 #include "pw_boot_armv7m/boot.h"
 #include "pw_malloc/malloc.h"
+#include "pw_preprocessor/compiler.h"
 #include "pw_sys_io_baremetal_stm32f429/init.h"
 
 void pw_boot_PreStaticMemoryInit() {
@@ -38,3 +39,10 @@
 }
 
 void pw_boot_PreMainInit() { pw_sys_io_Init(); }
+
+PW_NO_RETURN void pw_boot_PostMain() {
+  // In case main() returns, just sit here until the device is reset.
+  while (1) {
+  }
+  PW_UNREACHABLE;
+}