Updated debug interface to allow for halting with a 'cause'.

PiperOrigin-RevId: 667625877
Change-Id: I42e732f2859d0d482f892ac8bcf0a806258cf9fd
diff --git a/cheriot/BUILD b/cheriot/BUILD
index 8feb88d..3e2156a 100644
--- a/cheriot/BUILD
+++ b/cheriot/BUILD
@@ -591,6 +591,7 @@
         "@com_google_mpact-sim//mpact/sim/generic:core",
         "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
         "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/generic:debug_command_shell_interface",
         "@com_google_mpact-sim//mpact/sim/generic:instruction",
         "@com_google_mpact-sim//mpact/sim/proto:component_data_cc_proto",
         "@com_google_mpact-sim//mpact/sim/util/memory",
@@ -708,6 +709,7 @@
         "@com_google_mpact-riscv//riscv:stoull_wrapper",
         "@com_google_mpact-sim//mpact/sim/generic:core",
         "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+        "@com_google_mpact-sim//mpact/sim/generic:debug_command_shell_interface",
         "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
         "@com_google_mpact-sim//mpact/sim/proto:component_data_cc_proto",
         "@com_google_mpact-sim//mpact/sim/util/memory",
diff --git a/cheriot/cheriot_cli_forwarder.cc b/cheriot/cheriot_cli_forwarder.cc
index 633f763..6c5075b 100644
--- a/cheriot/cheriot_cli_forwarder.cc
+++ b/cheriot/cheriot_cli_forwarder.cc
@@ -80,6 +80,15 @@
 
 absl::Status CheriotCLIForwarder::Halt() { return cheriot_cli_top_->CLIHalt(); }
 
+absl::Status CheriotCLIForwarder::Halt(HaltReason halt_reason) {
+  cheriot_cli_top_->CLIRequestHalt(halt_reason, nullptr);
+  return absl::OkStatus();
+}
+
+absl::Status CheriotCLIForwarder::Halt(HaltReasonValueType halt_reason) {
+  cheriot_cli_top_->CLIRequestHalt(halt_reason, nullptr);
+  return absl::OkStatus();
+}
 absl::StatusOr<int> CheriotCLIForwarder::Step(int num) {
   return cheriot_cli_top_->CLIStep(num);
 }
diff --git a/cheriot/cheriot_cli_forwarder.h b/cheriot/cheriot_cli_forwarder.h
index a07386a..3da7fb6 100644
--- a/cheriot/cheriot_cli_forwarder.h
+++ b/cheriot/cheriot_cli_forwarder.h
@@ -71,6 +71,8 @@
   void SetBreakOnControlFlowChange(bool value) override;
   // Request that core stop running.
   absl::Status Halt() override;
+  absl::Status Halt(HaltReason halt_reason) override;
+  absl::Status Halt(HaltReasonValueType halt_reason) override;
   // Step the core by num instructions.
   absl::StatusOr<int> Step(int num) override;
   // Allow the core to free-run. The loop to run the instructions should be
diff --git a/cheriot/cheriot_renode.cc b/cheriot/cheriot_renode.cc
index aa417f8..88d2ea1 100644
--- a/cheriot/cheriot_renode.cc
+++ b/cheriot/cheriot_renode.cc
@@ -426,7 +426,7 @@
         cmd_shell_, cheriot_top_, mem_profiler_);
     cmd_shell_->AddCore(
         {static_cast<CheriotDebugInterface *>(cheriot_cli_forwarder_),
-         [this]() { return program_loader_; }});
+         [this]() { return program_loader_; }, cheriot_state_});
     cmd_shell_->AddCommand(
         instrumentation_control_->Usage(),
         absl::bind_front(&CheriotInstrumentationControl::PerformShellCommand,
diff --git a/cheriot/cheriot_state.cc b/cheriot/cheriot_state.cc
index e0c194d..685e6d0 100644
--- a/cheriot/cheriot_state.cc
+++ b/cheriot/cheriot_state.cc
@@ -655,9 +655,28 @@
   set_branch(true);
   // TODO(torerik): set next pc
   mstatus_->Submit();
+  // Set up interrupt info before incrementing the interrupt counter. That way
+  // any code that is triggered by the interrupt counter will see the updated
+  // interrupt info.
+  InterruptInfo info;
+  info.is_interrupt = is_interrupt;
+  info.cause = mcause_->GetUint32();
+  info.tval = mtval_->GetUint32();
+  info.epc = epc;
+  interrupt_info_list_.push_back(info);
+
   counter_interrupts_taken_.Increment(1);
 }
 
+// Called upon returning from an interrupt or exception.
+void CheriotState::SignalReturnFromInterrupt() {
+  // First increment the interrupt return counter. Then pop the interrupt info.
+  // This way any code that is triggered by the interrupt return counter will
+  // be able to access the interrupt info.
+  counter_interrupt_returns_.Increment(1);
+  interrupt_info_list_.pop_back();
+}
+
 // CheckForInterrupt is called whenever any relevant bits in the interrupt
 // enable and set bits are changed. It should always be scheduled to execute
 // from the function_delay_line, that way it is executed after an instruction
diff --git a/cheriot/cheriot_state.h b/cheriot/cheriot_state.h
index b24f5e3..6045039 100644
--- a/cheriot/cheriot_state.h
+++ b/cheriot/cheriot_state.h
@@ -19,6 +19,7 @@
 
 #include <any>
 #include <cstdint>
+#include <deque>
 #include <string>
 #include <string_view>
 #include <utility>
@@ -157,6 +158,15 @@
 
 using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
 
+// Struct to track interrupt/trap information.
+struct InterruptInfo {
+  bool is_interrupt;
+  uint32_t cause;
+  uint32_t tval;
+  uint32_t epc;
+};
+using InterruptInfoList = std::deque<InterruptInfo>;
+
 class CheriotState : public generic::ArchState {
  public:
   static int constexpr kCapRegQueueSizeMask = 0x11;
@@ -290,7 +300,7 @@
   // Indicates that the program has returned from handling an interrupt. This
   // decrements the interrupt handler depth and should be called by the
   // implementations of mret, sret, and uret.
-  void SignalReturnFromInterrupt() { counter_interrupt_returns_.Increment(1); }
+  void SignalReturnFromInterrupt();
 
   // Returns the depth of the interrupt handler currently being executed, or
   // zero if no interrupt handler is being executed.
@@ -399,7 +409,9 @@
   CheriotRegister *temp_reg() { return temp_reg_; }
   RiscVCsrInterface *mcause() { return mcause_; }
   RiscVCheri32PcSourceOperand *pc_src_operand() { return pc_src_operand_; }
-
+  const InterruptInfoList &interrupt_info_list() const {
+    return interrupt_info_list_;
+  }
   uint64_t revocation_mem_base() const { return revocation_mem_base_; }
   uint64_t revocation_ram_base() const { return revocation_ram_base_; }
 
@@ -451,6 +463,7 @@
   SimpleCounter<int64_t> counter_interrupts_taken_;
   SimpleCounter<int64_t> counter_interrupt_returns_;
   InterruptCode available_interrupt_code_ = InterruptCode::kNone;
+  InterruptInfoList interrupt_info_list_;
   // By default, execute in machine mode.
   PrivilegeMode privilege_mode_ = PrivilegeMode::kMachine;
   // Handles to frequently used CSRs.
diff --git a/cheriot/cheriot_top.cc b/cheriot/cheriot_top.cc
index 14d1cbf..6df56ad 100644
--- a/cheriot/cheriot_top.cc
+++ b/cheriot/cheriot_top.cc
@@ -216,6 +216,16 @@
   return absl::OkStatus();
 }
 
+absl::Status CheriotTop::Halt(HaltReason halt_reason) {
+  RequestHalt(halt_reason, nullptr);
+  return absl::OkStatus();
+}
+
+absl::Status CheriotTop::Halt(HaltReasonValueType halt_reason) {
+  RequestHalt(halt_reason, nullptr);
+  return absl::OkStatus();
+}
+
 absl::Status CheriotTop::StepPastBreakpoint() {
   uint64_t pc = state_->pc_operand()->AsUint64(0);
   // Disable the breakpoint.
diff --git a/cheriot/cheriot_top.h b/cheriot/cheriot_top.h
index 660d188..655bc94 100644
--- a/cheriot/cheriot_top.h
+++ b/cheriot/cheriot_top.h
@@ -73,6 +73,8 @@
 
   // Methods inherited from CoreDebugInterface.
   absl::Status Halt() override;
+  absl::Status Halt(HaltReason halt_reason) override;
+  absl::Status Halt(HaltReasonValueType halt_reason) override;
   absl::StatusOr<int> Step(int num) override;
   absl::Status Run() override;
   absl::Status Wait() override;
diff --git a/cheriot/debug_command_shell.cc b/cheriot/debug_command_shell.cc
index e1ea869..927bc34 100644
--- a/cheriot/debug_command_shell.cc
+++ b/cheriot/debug_command_shell.cc
@@ -35,6 +35,7 @@
 #include "absl/strings/string_view.h"
 #include "cheriot/cheriot_debug_interface.h"
 #include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
 #include "cheriot/cheriot_top.h"
 #include "cheriot/riscv_cheriot_enums.h"
 #include "cheriot/riscv_cheriot_register_aliases.h"
@@ -56,56 +57,37 @@
 
 DebugCommandShell::InterruptListener::InterruptListener(CoreAccess *core_access)
     : core_access_(core_access),
-      top_(static_cast<CheriotTop *>(core_access->debug_interface)),
+      state_(static_cast<CheriotState *>(core_access_->state)),
+      dbg_if_(static_cast<CheriotTop *>(core_access->debug_interface)),
       taken_listener_(
           absl::bind_front(&InterruptListener::SetTakenValue, this)),
       return_listener_(
           absl::bind_front(&InterruptListener::SetReturnValue, this)) {
-  top_->state()->counter_interrupts_taken()->AddListener(&taken_listener_);
-  top_->state()->counter_interrupt_returns()->AddListener(&return_listener_);
+  state_->counter_interrupts_taken()->AddListener(&taken_listener_);
+  state_->counter_interrupt_returns()->AddListener(&return_listener_);
 }
 
 void DebugCommandShell::InterruptListener::SetReturnValue(int64_t value) {
-  if (interrupt_info_list_.empty()) {
+  auto &interrupt_info_list = state_->interrupt_info_list();
+  if (interrupt_info_list.empty()) {
     LOG(ERROR) << "Interrupt stack is empty";
     return;
   }
-  auto info = interrupt_info_list_.front();
-  interrupt_info_list_.pop_front();
+  auto info = interrupt_info_list.back();
   // If breakpoints are enabled, then request a halt of the appropriate type.
   if (info.is_interrupt && interrupts_enabled_)
-    top_->RequestHalt(kInterruptReturn, nullptr);
+    (void)dbg_if_->Halt(kInterruptReturn);
   if (!info.is_interrupt && exceptions_enabled_)
-    top_->RequestHalt(kExceptionReturn, nullptr);
+    (void)dbg_if_->Halt(kExceptionReturn);
 }
 
 void DebugCommandShell::InterruptListener::SetTakenValue(int64_t value) {
-  InterruptInfo info;
-  bool ok = true;
-  // Read the values of the interrupt registers.
-  auto res = core_access_->debug_interface->ReadRegister("mcause");
-  ok &= res.ok();
-  if (ok) info.cause = res.value();
-
-  res = core_access_->debug_interface->ReadRegister("mtval");
-  ok &= res.ok();
-  if (ok) info.tval = res.value();
-
-  res = core_access_->debug_interface->ReadRegister("mepcc");
-  ok &= res.ok();
-  if (ok) info.epc = res.value();
-
-  if (!ok) {
-    LOG(ERROR) << "Failed to read interrupt registers";
-    return;
-  }
-  info.is_interrupt = (info.cause & 0x8000'0000u) != 0;
+  InterruptInfo info = state_->interrupt_info_list().back();
   // If breakpoints are enabled, the request a halt of the appropriate type.
   if (info.is_interrupt && interrupts_enabled_)
-    top_->RequestHalt(kInterruptTaken, nullptr);
+    (void)dbg_if_->Halt(kInterruptTaken);
   if (!info.is_interrupt && exceptions_enabled_)
-    top_->RequestHalt(kExceptionTaken, nullptr);
-  interrupt_info_list_.push_front(info);
+    (void)dbg_if_->Halt(kExceptionTaken);
 }
 
 // The constructor initializes all the regular expressions and the help string.
@@ -332,8 +314,9 @@
       }
       absl::StrAppend(&prompt, "\n");
     }
-    auto &info_list =
-        interrupt_listeners_[current_core_]->interrupt_info_list();
+    auto cheriot_state =
+        static_cast<CheriotState *>(core_access_[current_core_].state);
+    auto &info_list = cheriot_state->interrupt_info_list();
     int count = 0;
     for (auto iter = info_list.rbegin(); iter != info_list.rend(); ++iter) {
       auto const &info = *iter;
diff --git a/cheriot/debug_command_shell.h b/cheriot/debug_command_shell.h
index 71fc7b0..b58b1fe 100644
--- a/cheriot/debug_command_shell.h
+++ b/cheriot/debug_command_shell.h
@@ -32,7 +32,8 @@
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
-#include "cheriot/cheriot_top.h"
+#include "cheriot/cheriot_debug_interface.h"
+#include "cheriot/cheriot_state.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/counters_base.h"
 #include "mpact/sim/generic/debug_command_shell_interface.h"
@@ -88,14 +89,6 @@
     bool is_enabled;
   };
 
-  // Struct to track interrupt/trap information.
-  struct InterruptInfo {
-    bool is_interrupt;
-    uint32_t cause;
-    uint32_t tval;
-    uint32_t epc;
-  };
-
   // The interrupt listener class is used to track interrupts/exceptions and
   // returns from interrupts/exceptions, so that breakpoints can be set on these
   // events.
@@ -112,7 +105,6 @@
       absl::AnyInvocable<void(int64_t)> callback_;
     };
 
-    using InterruptInfoList = std::deque<InterruptInfo>;
     static constexpr uint32_t kInterruptTaken =
         *HaltReason::kUserSpecifiedMin + 1;
     static constexpr uint32_t kInterruptReturn =
@@ -128,19 +120,15 @@
     bool AreExceptionsEnabled() const { return exceptions_enabled_; }
     bool AreInterruptsEnabled() const { return interrupts_enabled_; }
 
-    const InterruptInfoList &interrupt_info_list() const {
-      return interrupt_info_list_;
-    }
-
    private:
     void SetReturnValue(int64_t value);
     void SetTakenValue(int64_t value);
 
     CoreAccess *core_access_;
-    CheriotTop *top_;
+    CheriotState *state_ = nullptr;
+    CheriotDebugInterface *dbg_if_ = nullptr;
     bool interrupts_enabled_ = false;
     bool exceptions_enabled_ = false;
-    InterruptInfoList interrupt_info_list_;
     Listener taken_listener_;
     Listener return_listener_;
   };
diff --git a/cheriot/mpact_cheriot.cc b/cheriot/mpact_cheriot.cc
index a15cfee..a24abce 100644
--- a/cheriot/mpact_cheriot.cc
+++ b/cheriot/mpact_cheriot.cc
@@ -464,7 +464,8 @@
   CheriotInstrumentationControl *cheriot_instrumentation_control = nullptr;
   if (interactive) {
     mpact::sim::cheriot::DebugCommandShell cmd_shell;
-    cmd_shell.AddCore({&cheriot_top, [&elf_loader]() { return &elf_loader; }});
+    cmd_shell.AddCore({&cheriot_top, [&elf_loader]() { return &elf_loader; },
+                       &cheriot_state});
     cheriot_instrumentation_control = new CheriotInstrumentationControl(
         &cmd_shell, &cheriot_top, memory_use_profiler);
     // Add custom command to interactive debug command shell.