Allow the error invocation handler count to be reset.

This lets long-running compartment invocations (for example, thread
entry points) determine that they are in a known-valid state.

Fixes #299
diff --git a/sdk/core/switcher/entry.S b/sdk/core/switcher/entry.S
index 5b6a5cb..ff5d3e7 100644
--- a/sdk/core/switcher/entry.S
+++ b/sdk/core/switcher/entry.S
@@ -992,6 +992,25 @@
 	csrr               a0, CSR_MSHWM
 	cret
 
+	.section .text, "ax", @progbits
+	.p2align 2
+	.type __Z39switcher_handler_invocation_count_resetv,@function
+__Z39switcher_handler_invocation_count_resetv:
+	// Trusted stack pointer in ca1
+	cspecialr          ca1, mtdc
+	// Offset of the current trusted stack frame to a1
+	clhu               a0, TrustedStack_offset_frameoffset(ca1)
+	addi               a0, a0, -TrustedStackFrame_size
+	// Current trusted stack frame to ca1, a0 is dead
+	cincoffset         ca1, ca1, a0
+	// Current invocation count (for return) in a0
+	clh                a0, TrustedStackFrame_offset_errorHandlerCount(ca1)
+	// Reset invocation count
+	csh                zero, TrustedStackFrame_offset_errorHandlerCount(ca1)
+	// Zero trusted stack frame pointer register
+	li                 a1, 0
+	cret
+
 // The linker expects export tables to start with space for cgp and pcc, then
 // the compartment error handler.  We should eventually remove that requirement
 // for library export tables, but since they don't consume RAM after loading
@@ -1026,3 +1045,4 @@
 export __Z28switcher_thread_hazard_slotsv
 export __Z13thread_id_getv
 export __Z25stack_lowest_used_addressv
+export __Z39switcher_handler_invocation_count_resetv
diff --git a/sdk/include/switcher.h b/sdk/include/switcher.h
index a1088b6..5bfa480 100644
--- a/sdk/include/switcher.h
+++ b/sdk/include/switcher.h
@@ -1,6 +1,7 @@
 #pragma once
 #include <cdefs.h>
 #include <stddef.h>
+#include <stdint.h>
 
 /**
  * Returns true if the trusted stack contains at least `requiredFrames` frames
@@ -56,3 +57,26 @@
  * compartment invocation.
  */
 __cheri_libcall ptraddr_t stack_lowest_used_address(void);
+
+/**
+ * Resets the switcher's count of invocations.
+ *
+ * Switcher place a limit on the number of times a compartment invocation may
+ * fault (default 512).  This prevents a compartment from getting stuck
+ * partially recovering from errors.  This function resets the count to zero.
+ * It should be called from outer compartments' run loops and from places where
+ * the caller is certain that error handling is making forward progress.
+ *
+ * Note: If this is called from an error handler that subsequently returns with
+ * with install-context, the switcher will subtract one from this and set the
+ * switcher error count to -1.  Because this is odd, the switcher will detect
+ * the next invocation of the error handler as a double fault and will force
+ * unwind.  Do not call this if you are currently in an error handler unless
+ * you are jumping out via some mechanism that does *not* involve returning to
+ * the switcher.
+ *
+ * Returns the previous value of the invocation count.  The low bit is set if
+ * an error handler is currently running, the remaining bits are the count.
+ */
+__cheri_libcall uint16_t switcher_handler_invocation_count_reset(void);
+
diff --git a/tests/crash_recovery-test.cc b/tests/crash_recovery-test.cc
index 9048335..884c8d5 100644
--- a/tests/crash_recovery-test.cc
+++ b/tests/crash_recovery-test.cc
@@ -3,27 +3,35 @@
 
 #define TEST_NAME "Crash recovery (main runner)"
 #include "crash_recovery.h"
+#include <atomic>
 #include <cheri.hh>
 #include <errno.h>
 
-int crashes = 0;
+int               crashes = 0;
+std::atomic<bool> expectFault;
 
 extern "C" enum ErrorRecoveryBehaviour
 compartment_error_handler(struct ErrorState *frame, size_t mcause, size_t mtval)
 {
-	debug_log("Test saw error for PCC {}", frame->pcc);
-	debug_log("Error cause: {}, mtval: {}", mcause, mtval);
+	crashes++;
 	if (mcause == 0x2)
 	{
+		if (expectFault)
+		{
+			expectFault = false;
+			frame->pcc  = static_cast<char *>(frame->pcc) + 2;
+			return ErrorRecoveryBehaviour::InstallContext;
+		}
 		debug_log("Test hit assertion failure, unwinding");
 		return ErrorRecoveryBehaviour::ForceUnwind;
 	}
+	debug_log("Test saw error for PCC {}", frame->pcc);
+	debug_log("Error cause: {}, mtval: {}", mcause, mtval);
 	TEST((mcause == 0x1c) && (mtval == 0),
 	     "mcause should be 0x1c (CHERI), is {}, mtval should be 0 (force "
 	     "unwind), is {})",
 	     mcause,
 	     mtval);
-	crashes++;
 	debug_log("Resuming test at failure location");
 	return ErrorRecoveryBehaviour::InstallContext;
 }
@@ -62,4 +70,25 @@
 	check_stack();
 	debug_log("Calling crashy compartment returned (crashes: {})", crashes);
 	TEST(crashes == 3, "Failed to notice crash");
+
+	ptraddr_t handlerCount = switcher_handler_invocation_count_reset();
+	TEST(handlerCount == crashes * 2,
+	     "Should have called handler 3 times (3 entries, 3 exits giving a "
+	     "total of {}), was {}",
+	     crashes * 2,
+	     handlerCount);
+
+	// By default, we will be force unwound if we exceed 512 error-handler
+	// invocations.
+	constexpr int MaxCrashes = 600;
+	for (int i = 0; i < MaxCrashes; i++)
+	{
+		switcher_handler_invocation_count_reset();
+		expectFault = true;
+		// Crash with a guaranteed 16-bit instruction.  This cannot use
+		// `__builtin_trap` because the compiler knows that `__builtin_trap`
+		// does not return and so will not generate code following it.
+		asm volatile("c.unimp");
+	}
+	TEST(crashes == 3 + MaxCrashes, "Failed to notice crash");
 }