From: Ruslan Ruslichenko <[email protected]> The patch adds infrastructure of fault injection API to plugins. The following capabilities introduced:
- MMIO overrides: Allows plugins to register callbacks that intercept MMIO accesses. - IRQ injection: Provides mechanism to raise or pulse hardware irq's directly to primary interrupt controller. - CPU Excheption injection: Provides API for injecting CPU exceptions. As of now only ARM targets supported. - Custom Fault Registry: Implements a registry allowing QEMU device models to expose custom, device-specific fault handlers. Plugins can trigger these dynamically by name. Signed-off-by: Ruslan Ruslichenko <[email protected]> --- include/plugins/qemu-plugin.h | 19 ++++++ include/qemu/plugin.h | 39 ++++++++++++ plugins/api.c | 27 ++++++++ plugins/fault.c | 116 ++++++++++++++++++++++++++++++++++ plugins/meson.build | 1 + 5 files changed, 202 insertions(+) create mode 100644 plugins/fault.c diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h index a68427536f..96e2787788 100644 --- a/include/plugins/qemu-plugin.h +++ b/include/plugins/qemu-plugin.h @@ -1255,6 +1255,25 @@ uint64_t qemu_plugin_get_virtual_clock_ns(void); QEMU_PLUGIN_API void qemu_plugin_timer_virt_ns(uint64_t time, void (*cb)(void*), void *opaque); +typedef bool (*qemu_plugin_mmio_override_cb_t)(uint64_t hwaddr, + unsigned size, + bool is_write, + uint64_t *value); + +QEMU_PLUGIN_API +void qemu_plugin_register_mmio_override_cb(qemu_plugin_id_t id, + qemu_plugin_mmio_override_cb_t cb); + +QEMU_PLUGIN_API +void qemu_plugin_inject_irq(int irq_num, int cpu, bool pulse); + +QEMU_PLUGIN_API +void qemu_plugin_inject_exception(int excp_index, uint32_t data); + +QEMU_PLUGIN_API +void qemu_plugin_trigger_custom_fault(const char *fault_name, void *target_data, + void *fault_data); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index ddd77bd82c..4cb01b2125 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -43,6 +43,11 @@ get_plugin_meminfo_rw(qemu_plugin_meminfo_t i) return i >> 16; } +typedef void (*plugin_irq_inject_cb) (void *opaque, int irq, + int cpu, bool pulse); + +typedef void (*plugin_custom_fault_cb)(void *target_data, void *fault_data); + #ifdef CONFIG_PLUGIN extern QemuOptsList qemu_plugin_opts; @@ -234,6 +239,27 @@ static inline enum qemu_plugin_cb_flags qemu_plugin_get_cb_flags(void) return current_cpu->neg.plugin_cb_flags; } +void plugin_register_mmio_override_cb(qemu_plugin_id_t id, + qemu_plugin_mmio_override_cb_t cb); + +bool plugin_mmio_override_cb_invoke(uint64_t hwaddr, + uint64_t size, + bool is_write, + uint64_t* data); + +void plugin_register_intc(void *opaque, plugin_irq_inject_cb cb); + +void plugin_inject_irq(int irq_num, int cpu, bool pulse); + +void plugin_inject_exception(int excp_index, uint32_t data); + +void plugin_register_custom_fault(const char *fault_name, + plugin_custom_fault_cb cb); + +void plugin_trigger_custom_fault(const char* fault_name, void *target_data, + void *fault_data); + + #else /* !CONFIG_PLUGIN */ static inline void qemu_plugin_add_opts(void) @@ -324,6 +350,19 @@ static inline void qemu_plugin_user_prefork_lock(void) static inline void qemu_plugin_user_postfork(bool is_child) { } +static inline bool plugin_mmio_override_cb_invoke(uint64_t hwaddr, + uint64_t size, + bool is_write, + void* data) +{ return false; } + +static void plugin_register_intc(void *opaque, plugin_irq_inject_cb cb) +{ } + +static void plugin_register_custom_fault(const char *fault_name, + plugin_custom_fault_cb cb) +{ } + #endif /* !CONFIG_PLUGIN */ #endif /* QEMU_PLUGIN_H */ diff --git a/plugins/api.c b/plugins/api.c index fa650e1219..0adeaa0bc3 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -687,3 +687,30 @@ void qemu_plugin_timer_virt_ns(uint64_t time, void (*cb)(void*), void *opaque) timer_mod(data->timer, time); } + +uint64_t qemu_plugin_get_virtual_clock_ns(void) +{ + return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +void qemu_plugin_register_mmio_override_cb(qemu_plugin_id_t id, + qemu_plugin_mmio_override_cb_t cb) +{ + plugin_register_mmio_override_cb(id, cb); +} + +void qemu_plugin_inject_irq(int irq_num, int cpu, bool pulse) +{ + plugin_inject_irq(irq_num, cpu, pulse); +} + +void qemu_plugin_inject_exception(int excp_index, uint32_t data) +{ + plugin_inject_exception(excp_index, data); +} + +void qemu_plugin_trigger_custom_fault(const char *fault_name, + void *target_data, void *fault_data) +{ + plugin_trigger_custom_fault(fault_name, target_data, fault_data); +} diff --git a/plugins/fault.c b/plugins/fault.c new file mode 100644 index 0000000000..8f7c1e1333 --- /dev/null +++ b/plugins/fault.c @@ -0,0 +1,116 @@ +/* + * Fault Injection Core Subsystem + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "cpu.h" +#include "qemu/main-loop.h" +#include "hw/core/irq.h" +#include "qemu/plugin.h" + +typedef struct { + qemu_plugin_id_t id; + qemu_plugin_mmio_override_cb_t cb; +} MMIOOverrideEntry; + +static GArray *mmio_callbacks = NULL; + +void *intc_opaque; +static plugin_irq_inject_cb irq_inject_cb = NULL; + +static GHashTable *fault_registry = NULL; + +void plugin_register_mmio_override_cb(qemu_plugin_id_t id, + qemu_plugin_mmio_override_cb_t cb) +{ + if (!mmio_callbacks) { + mmio_callbacks = g_array_new(FALSE, FALSE, + sizeof(MMIOOverrideEntry)); + } + + MMIOOverrideEntry entry = { .id = id, .cb = cb }; + g_array_append_val(mmio_callbacks, entry); +} + +bool plugin_mmio_override_cb_invoke(uint64_t hwaddr, + uint64_t size, + bool is_write, + uint64_t* data) +{ + if (!mmio_callbacks) { + return false; + } + + for (int i = 0; i < mmio_callbacks->len; ++i) { + MMIOOverrideEntry *entry = &g_array_index(mmio_callbacks, + MMIOOverrideEntry, i); + if (entry->cb(hwaddr, size, is_write, data)) { + /* Stop on first match */ + return true; + } + } + + return false; +} + +void plugin_register_intc(void *opaque, plugin_irq_inject_cb cb) +{ + intc_opaque = opaque; + irq_inject_cb = cb; +} + +void plugin_inject_irq(int irq_num, int cpu, bool pulse) +{ + if (!irq_inject_cb) { + return; + } + + bool locked = bql_locked(); + + if (!locked) { + bql_lock(); + } + + irq_inject_cb(intc_opaque, irq_num, cpu, pulse); + + if (!locked) { + bql_unlock(); + } +} + +void plugin_inject_exception(int excp_index, uint32_t data) +{ +#if defined (TARGET_ARM) + arm_cpu_inject_exception(excp_index, data); +#else + qemu_log_mask(LOG_UNIMP, + "FI: Injecting exception is not supported for this target\n"); +#endif +} + +void plugin_register_custom_fault(const char *fault_name, + plugin_custom_fault_cb cb){ + if (!fault_registry) { + fault_registry = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + } + + g_hash_table_insert(fault_registry, g_strdup(fault_name), cb); +} + +void plugin_trigger_custom_fault(const char* fault_name, void *target_data, + void *fault_data) +{ + plugin_custom_fault_cb cb = NULL; + + if (fault_registry) { + cb = g_hash_table_lookup(fault_registry, fault_name); + } + + if (cb) { + cb(target_data, fault_data); + } +} diff --git a/plugins/meson.build b/plugins/meson.build index 9899f166ee..8995ce5977 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -86,3 +86,4 @@ system_ss.add(files('api.c', 'core.c')) common_ss.add(files('loader.c')) +specific_ss.add(files('fault.c')) -- 2.43.0
