[PATCH] accel/tcg: TCG Plugin callback on a vCpu interrupt

Mikhail Tyutin posted 1 patch 1 year ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20231122121741.21087-1-m.tyutin@yadro.com
Maintainers: Richard Henderson <richard.henderson@linaro.org>, Paolo Bonzini <pbonzini@redhat.com>, "Alex Bennée" <alex.bennee@linaro.org>, Alexandre Iooss <erdnaxe@crans.org>, Mahmoud Mandour <ma.mandourr@gmail.com>
accel/tcg/cpu-exec.c         |  5 +++++
include/qemu/plugin-event.h  |  1 +
include/qemu/plugin.h        |  4 ++++
include/qemu/qemu-plugin.h   | 12 +++++++++++-
plugins/core.c               | 12 ++++++++++++
plugins/qemu-plugins.symbols |  1 +
6 files changed, 34 insertions(+), 1 deletion(-)
[PATCH] accel/tcg: TCG Plugin callback on a vCpu interrupt
Posted by Mikhail Tyutin 1 year ago
TCG Plugin callback to notify plugins when interrupt is triggered for
a vCpu. The plugin can optionally use this notification to see reason
of aborted instruction execution.

Signed-off-by: Mikhail Tyutin <m.tyutin@yadro.com>
---
 accel/tcg/cpu-exec.c         |  5 +++++
 include/qemu/plugin-event.h  |  1 +
 include/qemu/plugin.h        |  4 ++++
 include/qemu/qemu-plugin.h   | 12 +++++++++++-
 plugins/core.c               | 12 ++++++++++++
 plugins/qemu-plugins.symbols |  1 +
 6 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c
index c938eb96f8..9110f7e290 100644
--- a/accel/tcg/cpu-exec.c
+++ b/accel/tcg/cpu-exec.c
@@ -750,6 +750,7 @@ static inline bool cpu_handle_exception(CPUState *cpu, int *ret)
         if (replay_exception()) {
             CPUClass *cc = CPU_GET_CLASS(cpu);
             qemu_mutex_lock_iothread();
+            qemu_plugin_vcpu_interrupt_cb(cpu);
             cc->tcg_ops->do_interrupt(cpu);
             qemu_mutex_unlock_iothread();
             cpu->exception_index = -1;
@@ -829,6 +830,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
             /* Do nothing */
         } else if (interrupt_request & CPU_INTERRUPT_HALT) {
             replay_interrupt();
+            qemu_plugin_vcpu_interrupt_cb(cpu);
             cpu->interrupt_request &= ~CPU_INTERRUPT_HALT;
             cpu->halted = 1;
             cpu->exception_index = EXCP_HLT;
@@ -840,6 +842,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
             X86CPU *x86_cpu = X86_CPU(cpu);
             CPUArchState *env = &x86_cpu->env;
             replay_interrupt();
+            qemu_plugin_vcpu_interrupt_cb(cpu);
             cpu_svm_check_intercept_param(env, SVM_EXIT_INIT, 0, 0);
             do_cpu_init(x86_cpu);
             cpu->exception_index = EXCP_HALTED;
@@ -849,6 +852,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
 #else
         else if (interrupt_request & CPU_INTERRUPT_RESET) {
             replay_interrupt();
+            qemu_plugin_vcpu_interrupt_cb(cpu);
             cpu_reset(cpu);
             qemu_mutex_unlock_iothread();
             return true;
@@ -866,6 +870,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
                 if (need_replay_interrupt(interrupt_request)) {
                     replay_interrupt();
                 }
+                qemu_plugin_vcpu_interrupt_cb(cpu);
                 /*
                  * After processing the interrupt, ensure an EXCP_DEBUG is
                  * raised when single-stepping so that GDB doesn't miss the
diff --git a/include/qemu/plugin-event.h b/include/qemu/plugin-event.h
index 7056d8427b..fe054c25dd 100644
--- a/include/qemu/plugin-event.h
+++ b/include/qemu/plugin-event.h
@@ -16,6 +16,7 @@ enum qemu_plugin_event {
     QEMU_PLUGIN_EV_VCPU_TB_TRANS,
     QEMU_PLUGIN_EV_VCPU_IDLE,
     QEMU_PLUGIN_EV_VCPU_RESUME,
+    QEMU_PLUGIN_EV_VCPU_INTERRUPT,
     QEMU_PLUGIN_EV_VCPU_SYSCALL,
     QEMU_PLUGIN_EV_VCPU_SYSCALL_RET,
     QEMU_PLUGIN_EV_FLUSH,
diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h
index 7fdc3a4849..f942e45f41 100644
--- a/include/qemu/plugin.h
+++ b/include/qemu/plugin.h
@@ -190,6 +190,7 @@ void qemu_plugin_vcpu_exit_hook(CPUState *cpu);
 void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb);
 void qemu_plugin_vcpu_idle_cb(CPUState *cpu);
 void qemu_plugin_vcpu_resume_cb(CPUState *cpu);
+void qemu_plugin_vcpu_interrupt_cb(CPUState *cpu);
 void
 qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1,
                          uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5,
@@ -270,6 +271,9 @@ static inline void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
 static inline void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
 { }
 
+static inline void qemu_plugin_vcpu_interrupt_cb(CPUState *cpu)
+{ }
+
 static inline void
 qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
                          uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6,
diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
index 4daab6efd2..4b978f98f4 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -54,7 +54,7 @@ typedef uint64_t qemu_plugin_id_t;
 
 extern QEMU_PLUGIN_EXPORT int qemu_plugin_version;
 
-#define QEMU_PLUGIN_VERSION 1
+#define QEMU_PLUGIN_VERSION 2
 
 /**
  * struct qemu_info_t - system information for plugins
@@ -215,6 +215,16 @@ QEMU_PLUGIN_API
 void qemu_plugin_register_vcpu_resume_cb(qemu_plugin_id_t id,
                                          qemu_plugin_vcpu_simple_cb_t cb);
 
+/**
+ * qemu_plugin_register_vcpu_interrupt_cb() - register a vCPU interrupt callback
+ * @id: plugin ID
+ * @cb: callback function
+ *
+ * The @cb function is called every time an interrupt is triggered on given vCPU.
+ */
+void qemu_plugin_register_vcpu_interrupt_cb(qemu_plugin_id_t id,
+                                            qemu_plugin_vcpu_simple_cb_t cb);
+
 /** struct qemu_plugin_tb - Opaque handle for a translation block */
 struct qemu_plugin_tb;
 /** struct qemu_plugin_insn - Opaque handle for a translated instruction */
diff --git a/plugins/core.c b/plugins/core.c
index 49588285dd..3f9d273613 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -102,6 +102,7 @@ static void plugin_vcpu_cb__simple(CPUState *cpu, enum qemu_plugin_event ev)
     case QEMU_PLUGIN_EV_VCPU_EXIT:
     case QEMU_PLUGIN_EV_VCPU_IDLE:
     case QEMU_PLUGIN_EV_VCPU_RESUME:
+    case QEMU_PLUGIN_EV_VCPU_INTERRUPT:
         /* iterate safely; plugins might uninstall themselves at any time */
         QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
             qemu_plugin_vcpu_simple_cb_t func = cb->f.vcpu_simple;
@@ -399,6 +400,11 @@ void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
     plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME);
 }
 
+void qemu_plugin_vcpu_interrupt_cb(CPUState *cpu)
+{
+    plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_INTERRUPT);
+}
+
 void qemu_plugin_register_vcpu_idle_cb(qemu_plugin_id_t id,
                                        qemu_plugin_vcpu_simple_cb_t cb)
 {
@@ -411,6 +417,12 @@ void qemu_plugin_register_vcpu_resume_cb(qemu_plugin_id_t id,
     plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_RESUME, cb);
 }
 
+void qemu_plugin_register_vcpu_interrupt_cb(qemu_plugin_id_t id,
+                                            qemu_plugin_vcpu_simple_cb_t cb)
+{
+    plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_INTERRUPT, cb);
+}
+
 void qemu_plugin_register_flush_cb(qemu_plugin_id_t id,
                                    qemu_plugin_simple_cb_t cb)
 {
diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols
index 71f6c90549..c8621f9950 100644
--- a/plugins/qemu-plugins.symbols
+++ b/plugins/qemu-plugins.symbols
@@ -30,6 +30,7 @@
   qemu_plugin_register_vcpu_mem_cb;
   qemu_plugin_register_vcpu_mem_inline;
   qemu_plugin_register_vcpu_resume_cb;
+  qemu_plugin_register_vcpu_interrupt_cb;
   qemu_plugin_register_vcpu_syscall_cb;
   qemu_plugin_register_vcpu_syscall_ret_cb;
   qemu_plugin_register_vcpu_tb_exec_cb;
-- 
2.34.1
Re: [PATCH] accel/tcg: TCG Plugin callback on a vCpu interrupt
Posted by Alex Bennée 11 months, 2 weeks ago
Mikhail Tyutin <m.tyutin@yadro.com> writes:

> TCG Plugin callback to notify plugins when interrupt is triggered for
> a vCpu. The plugin can optionally use this notification to see reason
> of aborted instruction execution.
>
> Signed-off-by: Mikhail Tyutin <m.tyutin@yadro.com>
> ---
>  accel/tcg/cpu-exec.c         |  5 +++++
>  include/qemu/plugin-event.h  |  1 +
>  include/qemu/plugin.h        |  4 ++++
>  include/qemu/qemu-plugin.h   | 12 +++++++++++-
>  plugins/core.c               | 12 ++++++++++++
>  plugins/qemu-plugins.symbols |  1 +
>  6 files changed, 34 insertions(+), 1 deletion(-)
>
> diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c
> index c938eb96f8..9110f7e290 100644
> --- a/accel/tcg/cpu-exec.c
> +++ b/accel/tcg/cpu-exec.c

The biggest problem I have with this approach is we are adding to an
already overly complex exception/interrupt code in the main loop.

> @@ -750,6 +750,7 @@ static inline bool cpu_handle_exception(CPUState *cpu, int *ret)
>          if (replay_exception()) {
>              CPUClass *cc = CPU_GET_CLASS(cpu);
>              qemu_mutex_lock_iothread();
> +            qemu_plugin_vcpu_interrupt_cb(cpu);
>              cc->tcg_ops->do_interrupt(cpu);

This is at least what we think of as a traditional IRQ. Something that
interrupts the flow of the processor and after some target specific
processing goes somewhere else.

>              qemu_mutex_unlock_iothread();
>              cpu->exception_index = -1;
> @@ -829,6 +830,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
>              /* Do nothing */
>          } else if (interrupt_request & CPU_INTERRUPT_HALT) {
>              replay_interrupt();
> +            qemu_plugin_vcpu_interrupt_cb(cpu);
>              cpu->interrupt_request &= ~CPU_INTERRUPT_HALT;
>              cpu->halted = 1;

This isn't really an interrupt - its used by a selection of the various
architectures to bring the vCPU to a halted state. However it's not the
only way to halt the CPU as grepping for "->halted = 1;" will show you.
If we successfully halt we'll eventually end up calling:

  qemu_plugin_vcpu_idle_cb() 

>              cpu->exception_index = EXCP_HLT;
> @@ -840,6 +842,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
>              X86CPU *x86_cpu = X86_CPU(cpu);
>              CPUArchState *env = &x86_cpu->env;
>              replay_interrupt();
> +            qemu_plugin_vcpu_interrupt_cb(cpu);
>              cpu_svm_check_intercept_param(env, SVM_EXIT_INIT, 0, 0);
>              do_cpu_init(x86_cpu);
>              cpu->exception_index = EXCP_HALTED;

This is some x86 specific hack that we've not managed to find a sensible
way to excise out of the common code. 

> @@ -849,6 +852,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
>  #else
>          else if (interrupt_request & CPU_INTERRUPT_RESET) {
>              replay_interrupt();
> +            qemu_plugin_vcpu_interrupt_cb(cpu);
>              cpu_reset(cpu);

This again is not an IRQ really but another (minority) method for
generating a cold reset in the vCPU. There may be an argument to add a
plugin hook in cpu_reset() but I suspect most of the calls to it would
be artificial artefacts of the way to start and configure vCPUS as we
bring objects up.

>              qemu_mutex_unlock_iothread();
>              return true;
> @@ -866,6 +870,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu,
>                  if (need_replay_interrupt(interrupt_request)) {
>                      replay_interrupt();
>                  }
> +                qemu_plugin_vcpu_interrupt_cb(cpu);

This may be an interrupt but we also may have restarted the loop via
cpu_loop_exit() so missing the rest of the logic. Some of these handlers
will also call cc->tcg_ops->do_interrupt(cpu) like above.

<snip>
>  
> +void qemu_plugin_vcpu_interrupt_cb(CPUState *cpu)
> +{
> +    plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_INTERRUPT);
> +}
> +
>  void qemu_plugin_register_vcpu_idle_cb(qemu_plugin_id_t id,
>                                         qemu_plugin_vcpu_simple_cb_t cb)
>  {
> @@ -411,6 +417,12 @@ void qemu_plugin_register_vcpu_resume_cb(qemu_plugin_id_t id,
>      plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_RESUME, cb);
>  }
>  
> +void qemu_plugin_register_vcpu_interrupt_cb(qemu_plugin_id_t id,
> +                                            qemu_plugin_vcpu_simple_cb_t cb)
> +{
> +    plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_INTERRUPT, cb);
> +}
> +
>  void qemu_plugin_register_flush_cb(qemu_plugin_id_t id,
>                                     qemu_plugin_simple_cb_t cb)
>  {
> diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols
> index 71f6c90549..c8621f9950 100644
> --- a/plugins/qemu-plugins.symbols
> +++ b/plugins/qemu-plugins.symbols
> @@ -30,6 +30,7 @@
>    qemu_plugin_register_vcpu_mem_cb;
>    qemu_plugin_register_vcpu_mem_inline;
>    qemu_plugin_register_vcpu_resume_cb;
> +  qemu_plugin_register_vcpu_interrupt_cb;
>    qemu_plugin_register_vcpu_syscall_cb;
>    qemu_plugin_register_vcpu_syscall_ret_cb;
>    qemu_plugin_register_vcpu_tb_exec_cb;

When we add new callbacks we should at least use them in one of the
plugins to check they work. 

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro