From: Ruslan Ruslichenko <ruslichenko.r@gmail.com>
To: qemu-devel@nongnu.org
Cc: qemu-arm@nongnu.org, artem_mygaiev@epam.com,
volodymyr_babchuk@epam.com, alex.bennee@linaro.org,
peter.maydell@linaro.org, pierrick.bouvier@linaro.org,
philmd@linaro.org, Ruslan_Ruslichenko@epam.com
Subject: [RFC PATCH 8/9] contrib/plugins: Add fault injection plugin
Date: Wed, 18 Mar 2026 11:46:39 +0100 [thread overview]
Message-ID: <20260318104640.239752-9-ruslichenko.r@gmail.com> (raw)
In-Reply-To: <20260318104640.239752-1-ruslichenko.r@gmail.com>
From: Ruslan Ruslichenko <Ruslan_Ruslichenko@epam.com>
Introduce Fault injection plugin for AArch64 targets. This
plugin provides a framework for testing guest OS by injecting
hardware-level faults during execution.
The plugin can be configured either statically via an XML file
at boot or dynamically at runtime via a UNIX socket.
Supported triggers:
- PC: Triggers on instruction execution at specific address.
- SYS_REG: Intercepts System Registers reads (e.g. mrs) and
modifies read results to configured value.
- RAM: Triggers on physical memory accesses.
- MMIO: Intercepts memory-mapped I/O.
- Timer: Triggers at a specific guest virtual clock time (ns).
Supported targets (injected faults):
- CPU_REG: Corrupts general-purpose CPU registers.
- RAM/MMIO: Modifies result of memory reads.
- IRQ: Inject hardware irqs on the primary INTC.
- EXCP: Inject CPU exceptions (e.g., Serror).
- Custom: Triggers device-specific fault handlers registered by
device models.
Signed-off-by: Ruslan Ruslichenko <Ruslan_Ruslichenko@epam.com>
---
contrib/plugins/fault_injection.c | 772 ++++++++++++++++++++++++++++++
contrib/plugins/meson.build | 1 +
2 files changed, 773 insertions(+)
create mode 100644 contrib/plugins/fault_injection.c
diff --git a/contrib/plugins/fault_injection.c b/contrib/plugins/fault_injection.c
new file mode 100644
index 0000000000..6fa09fd359
--- /dev/null
+++ b/contrib/plugins/fault_injection.c
@@ -0,0 +1,772 @@
+/*
+ * Fault Injection Plugin
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ctype.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "qemu/osdep.h"
+#include <qemu-plugin.h>
+
+#include "glib/gmarkup.h"
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+typedef enum {
+ TRIGGER_ON_PC = 0,
+ TRIGGER_ON_SYSREG,
+ TRIGGER_ON_RAM,
+ TRIGGER_ON_MMIO,
+ TRIGGER_ON_TIMER
+} FaultTrigger;
+
+typedef enum {
+ TARGET_EMPTY = 0,
+ TARGET_CPU_REG,
+ TARGET_RAM,
+ TARGET_MMIO,
+ TARGET_IRQ,
+ TARGET_EXCP,
+ TARGET_CUSTOM
+} FaultTarget;
+
+typedef struct {
+ FaultTarget target;
+ uint64_t target_data;
+
+ FaultTrigger trigger;
+ uint64_t trigger_condition;
+ gchar *trigger_condition_str;
+
+ uint64_t fault_data;
+ gchar *fault_name;
+
+ uint8_t size;
+ uint8_t cpu;
+ gchar *irq_type;
+} FaultConfig;
+
+typedef struct {
+ uint64_t hwaddr;
+ uint64_t value;
+ uint8_t size;
+} MmioOverrideConfig;
+
+#define FI_LOG(...) do { \
+ g_autofree gchar *__msg = g_strdup_printf(__VA_ARGS__); \
+ qemu_plugin_outs(__msg); \
+} while (0)
+
+static bool plugin_is_shutting_down = false;
+static int socket_fd = -1;
+
+static GRWLock trigger_lock;
+
+GHashTable *pc_faults;
+GHashTable *mem_faults;
+GHashTable *sys_reg_faults;
+
+static GRWLock mmio_override_lock;
+static GRWLock sysreg_lock;
+
+GHashTable *mmio_override;
+
+static struct qemu_plugin_register *gp_registers[31];
+
+static void register_pc_trigger(FaultConfig* fc);
+static void register_mmio_override(FaultConfig *fc);
+
+static void fc_free(FaultConfig *fc);
+
+static bool apply_mmio_override(uint64_t hwaddr, unsigned size, bool is_write,
+ uint64_t *value)
+{
+ g_rw_lock_reader_lock(&mmio_override_lock);
+
+ MmioOverrideConfig *conf = g_hash_table_lookup(mmio_override, &hwaddr);
+ if (!conf) {
+ g_rw_lock_reader_unlock(&mmio_override_lock);
+ return false;
+ }
+
+ *value = conf->value;
+
+ g_rw_lock_reader_unlock(&mmio_override_lock);
+
+ return true;
+}
+
+static bool mmio_override_cb(uint64_t hwaddr, unsigned size, bool is_write,
+ uint64_t *value)
+{
+ if (is_write) {
+ return false;
+ }
+
+ return apply_mmio_override(hwaddr, size, is_write, value);
+}
+
+static void cpu_write_reg(int reg_id, uint64_t value)
+{
+ g_assert(reg_id >= 0 && reg_id <= 30);
+
+ g_autoptr(GByteArray) buf = g_byte_array_new();
+
+ g_byte_array_set_size(buf, 8);
+
+ memcpy(buf->data, &value, 8);
+
+ bool success = qemu_plugin_write_register(gp_registers[reg_id], buf);
+ if (!success) {
+ FI_LOG("FI: Failed to write register\n");
+ }
+}
+
+static void cpu_write_mem(uint64_t addr, uint64_t data, uint8_t size)
+{
+ g_autoptr(GByteArray) buf = g_byte_array_new();
+
+ g_byte_array_set_size(buf, size);
+
+ memcpy(buf->data, &data, size);
+
+ bool success = qemu_plugin_write_memory_vaddr(addr, buf);
+ if (!success) {
+ FI_LOG("FI: Failed to write memory\n");
+ }
+}
+
+static void inject_irq(FaultConfig *fc)
+{
+ int irq_num = fc->target_data;
+
+ if (!fc->irq_type || !g_strcmp0(fc->irq_type, "SPI")) {
+ irq_num += 32;
+ } else if (!g_strcmp0(fc->irq_type, "PPI")) {
+ irq_num += 16;
+ } else if (!g_strcmp0(fc->irq_type, "SGI")) {
+ /* skip */
+ } else {
+ FI_LOG("FI: Unknown IRQ type: %s\n", fc->irq_type);
+ }
+
+ qemu_plugin_inject_irq(irq_num, fc->cpu, fc->fault_data);
+
+}
+
+static void inject_fault(FaultConfig* fc)
+{
+ switch (fc->target) {
+ case TARGET_CPU_REG:
+ cpu_write_reg(fc->target_data, fc->fault_data);
+ break;
+ case TARGET_RAM:
+ cpu_write_mem(fc->target_data, fc->fault_data, fc->size);
+ break;
+ case TARGET_MMIO:
+ register_mmio_override(fc);
+ break;
+ case TARGET_IRQ:
+ inject_irq(fc);
+ break;
+ case TARGET_EXCP:
+ qemu_plugin_inject_exception(fc->target_data, fc->fault_data);
+ break;
+ case TARGET_CUSTOM:
+ qemu_plugin_trigger_custom_fault(fc->fault_name,
+ &fc->target_data, &fc->fault_data);
+ break;
+ default:
+ FI_LOG("FI: Unsupported fault type\n");
+ break;
+ }
+}
+
+static void timed_fault_timer_cb(void* data)
+{
+ FaultConfig* fc = (FaultConfig*)data;
+
+ inject_fault(fc);
+
+ fc_free(fc);
+}
+
+static void vcpu_mem_cb(unsigned int vcpu_index,
+ qemu_plugin_meminfo_t info,
+ uint64_t vaddr, void *userdata)
+{
+ GSList *fault_list;
+
+ g_rw_lock_reader_lock(&trigger_lock);
+
+ fault_list = g_hash_table_lookup(mem_faults, &vaddr);
+ for (GSList *entry = fault_list; entry != NULL; entry = entry->next) {
+ FaultConfig *fc = (FaultConfig *)entry->data;
+
+ inject_fault(fc);
+ }
+
+ g_rw_lock_reader_unlock(&trigger_lock);
+}
+
+static void vcpu_insn_exec_cb(unsigned int vcpu_index, void *data)
+{
+ uint64_t insn_vaddr = (uint64_t)data;
+ GSList *fault_list;
+
+ g_rw_lock_reader_lock(&trigger_lock);
+
+ fault_list = g_hash_table_lookup(pc_faults,
+ &insn_vaddr);
+
+ for (GSList *l = fault_list; l != NULL; l = l->next) {
+ FaultConfig *fc = (FaultConfig *)l->data;
+
+ inject_fault(fc);
+ }
+
+ g_rw_lock_reader_unlock(&trigger_lock);
+}
+
+#define MRS_OPCODE 0xD5300000
+#define MRS_OPCODE_MASK 0xFFF00000
+
+static void handle_sysreg_fault(struct qemu_plugin_insn *insn, uint64_t insn_vaddr)
+{
+ FaultConfig *fc;
+ uint32_t raw_opcode;
+ size_t data_size = qemu_plugin_insn_data(insn, &raw_opcode, sizeof(raw_opcode));
+ if (data_size < sizeof(raw_opcode)) {
+ return;
+ }
+
+ uint32_t opcode = GUINT32_FROM_LE(raw_opcode);
+
+ if ((opcode & MRS_OPCODE_MASK) != MRS_OPCODE) {
+ return;
+ }
+
+ char *disas = qemu_plugin_insn_disas(insn);
+ if (!disas) {
+ return;
+ }
+
+ int dest_reg;
+ char sysreg_name[32] = { 0 };
+
+ if (sscanf(disas, "mrs x%d, %31s", &dest_reg, sysreg_name) == 2) {
+ uint64_t fault_data;
+ bool found = false;
+
+ g_rw_lock_reader_lock(&sysreg_lock);
+
+ fc = g_hash_table_lookup(sys_reg_faults, sysreg_name);
+ if (fc) {
+ fault_data = fc->fault_data;
+ found = true;
+ }
+
+ g_rw_lock_reader_unlock(&sysreg_lock);
+
+ if (found) {
+ /*
+ * WA: For CPU system registers, injecting fault to destination
+ * gp register on next PC
+ */
+ FaultConfig *dyn_pc_fault = g_new0(FaultConfig, 1);
+
+ dyn_pc_fault->trigger = TRIGGER_ON_PC;
+ dyn_pc_fault->trigger_condition = insn_vaddr + 4;
+ dyn_pc_fault->target = TARGET_CPU_REG;
+ dyn_pc_fault->target_data = dest_reg;
+ dyn_pc_fault->fault_data = fault_data;
+
+ register_pc_trigger(dyn_pc_fault);
+ }
+ }
+
+ g_free(disas);
+}
+
+static void vcpu_tb_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+ for(int i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
+ struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+ uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
+ GSList *fault_list;
+
+ qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem_cb,
+ QEMU_PLUGIN_CB_NO_REGS, QEMU_PLUGIN_MEM_RW, NULL);
+
+ handle_sysreg_fault(insn, insn_vaddr);
+
+ g_rw_lock_reader_lock(&trigger_lock);
+
+ fault_list = g_hash_table_lookup(pc_faults,
+ &insn_vaddr);
+
+ if (fault_list) {
+ qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec_cb,
+ QEMU_PLUGIN_CB_RW_REGS,
+ (void *)insn_vaddr);
+ }
+
+ g_rw_lock_reader_unlock(&trigger_lock);
+ }
+}
+
+static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index)
+{
+ if (vcpu_index) {
+ /* Init reg's and mem watchpoints only once, with CPU 0 */
+ return;
+ }
+
+ g_autoptr(GArray) reg_list = qemu_plugin_get_registers();
+
+ for (int i = 0; i < reg_list->len; ++i) {
+ qemu_plugin_reg_descriptor *rd = &g_array_index(reg_list,
+ qemu_plugin_reg_descriptor, i);
+
+ if (rd->name[0] == 'x' && isdigit(rd->name[1])) {
+ int reg_ind = atoi(&rd->name[1]);
+
+ if (reg_ind >= 0 && reg_ind <= 30) {
+ gp_registers[reg_ind] = rd->handle;
+ }
+ }
+ }
+}
+
+static void register_mmio_override(FaultConfig *fc)
+{
+ g_rw_lock_writer_lock(&mmio_override_lock);
+
+ MmioOverrideConfig *curr_conf = g_hash_table_lookup(mmio_override,
+ &fc->target_data);
+ if (curr_conf) {
+ curr_conf->value = fc->fault_data;
+ curr_conf->size = fc->size;
+ } else {
+ MmioOverrideConfig *new_conf = g_new0(MmioOverrideConfig, 1);
+
+ new_conf->hwaddr = fc->target_data;
+ new_conf->value = fc->fault_data;
+ new_conf->size = fc->size;
+
+ g_hash_table_insert(mmio_override, &new_conf->hwaddr,
+ new_conf);
+ }
+
+ g_rw_lock_writer_unlock(&mmio_override_lock);
+}
+
+static void register_sysreg_override(FaultConfig *fc)
+{
+ g_rw_lock_writer_lock(&sysreg_lock);
+
+ FaultConfig *old_fc = g_hash_table_lookup(sys_reg_faults,
+ fc->trigger_condition_str);
+ g_hash_table_replace(sys_reg_faults,
+ fc->trigger_condition_str,
+ fc);
+
+ if (old_fc) {
+ fc_free(old_fc);
+ }
+
+ g_rw_lock_writer_unlock(&sysreg_lock);
+}
+
+static void register_ram_trigger(FaultConfig* fc)
+{
+
+ g_rw_lock_writer_lock(&trigger_lock);
+
+ GSList *mem_list = g_hash_table_lookup(mem_faults, &fc->trigger_condition);
+
+ mem_list = g_slist_append(mem_list, fc);
+ g_hash_table_insert(mem_faults,
+ &fc->trigger_condition, mem_list);
+
+ g_rw_lock_writer_unlock(&trigger_lock);
+
+}
+
+static void register_pc_trigger(FaultConfig* fc)
+{
+ g_rw_lock_writer_lock(&trigger_lock);
+
+ bool duplicate = false;
+ GSList *pc_list = g_hash_table_lookup(pc_faults,
+ &fc->trigger_condition);
+
+ for (GSList *l = pc_list; l != NULL; l = l->next) {
+ FaultConfig *existing = (FaultConfig *)l->data;
+
+ if (existing->target == fc->target &&
+ existing->target_data == fc->target_data &&
+ existing->fault_data == fc->fault_data) {
+ duplicate = true;
+ break;
+ }
+ }
+
+ if (!duplicate) {
+ pc_list = g_slist_append(pc_list, fc);
+ g_hash_table_insert(pc_faults, &fc->trigger_condition,
+ pc_list);
+ } else {
+ fc_free(fc);
+ }
+
+ g_rw_lock_writer_unlock(&trigger_lock);
+
+}
+
+static bool register_fault(FaultConfig *fc)
+{
+ FaultTrigger trigger_type = fc->trigger;
+
+ if (fc->target == TARGET_CUSTOM && !fc->fault_name) {
+ FI_LOG("FI: fault_name needed for custom targets\n");
+ return false;
+ }
+
+ if (!fc->size) {
+ fc->size = sizeof(fc->fault_data);
+ }
+
+ switch (fc->trigger) {
+ case TRIGGER_ON_PC:
+ register_pc_trigger(fc);
+ break;
+ case TRIGGER_ON_SYSREG:
+ if (fc->target != TARGET_EMPTY) {
+ FI_LOG("FI: SYS_REG faults does not support target\n");
+ return false;
+ }
+
+ register_sysreg_override(fc);
+ break;
+ case TRIGGER_ON_RAM:
+ if (fc->target == TARGET_EMPTY) {
+ /* Allow short form for RAM triggers to override same memory */
+ fc->target = TARGET_RAM;
+ fc->target_data = fc->trigger_condition;
+ }
+
+ register_ram_trigger(fc);
+ break;
+ case TRIGGER_ON_MMIO:
+ if (fc->target != TARGET_EMPTY) {
+ FI_LOG("FI: No target support for MMIO trigger for now\n");
+ return false;
+ }
+
+ register_mmio_override(fc);
+ fc_free(fc);
+ break;
+ case TRIGGER_ON_TIMER:
+ if (fc->target == TARGET_CPU_REG) {
+ FI_LOG("FI: CPU_REG is invalid for TIMER trigger\n");
+ return false;
+ }
+ qemu_plugin_timer_virt_ns(fc->trigger_condition,
+ timed_fault_timer_cb, fc);
+ break;
+ default:
+ /* skip */
+ break;
+ }
+
+ if (trigger_type == TRIGGER_ON_PC || trigger_type == TRIGGER_ON_SYSREG) {
+ qemu_plugin_flush_tb_cache();
+ }
+
+ return true;
+}
+
+static void fc_free(FaultConfig *fc)
+{
+ if (!fc) {
+ return;
+ }
+
+ g_free(fc->trigger_condition_str);
+ g_free(fc->fault_name);
+ g_free(fc->irq_type);
+
+ g_free(fc);
+}
+
+static void xml_start_elem(GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ if (!g_strcmp0(element_name, "Fault")) {
+ FaultConfig *fc = g_new0(FaultConfig, 1);
+
+ for (int i = 0; attribute_names[i] != NULL; i++) {
+ const char *key = attribute_names[i];
+ const char *value = attribute_values[i];
+
+ if (!g_strcmp0(key, "target")) {
+ if (!g_strcmp0(value, "CPU_REG")) {
+ fc->target = TARGET_CPU_REG;
+ } else if (!g_strcmp0(value, "RAM")) {
+ fc->target = TARGET_RAM;
+ } else if (!g_strcmp0(value, "MMIO")) {
+ fc->target = TARGET_MMIO;
+ } else if (!g_strcmp0(value, "IRQ")) {
+ fc->target = TARGET_IRQ;
+ } else if (!g_strcmp0(value, "EXCP")) {
+ fc->target = TARGET_EXCP;
+ } else if (!g_strcmp0(value, "CUSTOM")) {
+ fc->target = TARGET_CUSTOM;
+ } else {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "FI: Unknown target type '%s'", value);
+ fc_free(fc);
+ return;
+ }
+ } else if (!g_strcmp0(key, "trigger")) {
+ if (!g_strcmp0(value, "PC")) {
+ fc->trigger = TRIGGER_ON_PC;
+ } else if (!g_strcmp0(value, "SYS_REG")) {
+ fc->trigger = TRIGGER_ON_SYSREG;
+ } else if (!g_strcmp0(value, "RAM")) {
+ fc->trigger = TRIGGER_ON_RAM;
+ } else if (!g_strcmp0(value, "MMIO")) {
+ fc->trigger = TRIGGER_ON_MMIO;
+ } else if (!g_strcmp0(value, "TIMER")) {
+ fc->trigger = TRIGGER_ON_TIMER;
+ } else {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "FI: Unknown trigger type: '%s'", value);
+ fc_free(fc);
+ return;
+ }
+ } else if (!g_strcmp0(key, "target_data")) {
+ fc->target_data = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "trigger_condition")) {
+ fc->trigger_condition_str = g_strdup(value);
+ fc->trigger_condition = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "fault_data")) {
+ fc->fault_data = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "size")) {
+ fc->size = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "cpu")) {
+ fc->cpu = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "irq_type")) {
+ fc->irq_type = g_strdup(value);
+ } else if (!g_strcmp0(key, "fault_name")) {
+ fc->fault_name = g_strdup(value);
+ }
+ }
+
+ if (!register_fault(fc)) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "FI: Failed to register fault");
+ fc_free(fc);
+ return;
+ }
+ }
+}
+
+static GMarkupParser parser = {
+ .start_element = xml_start_elem,
+};
+
+static void *ipc_listener_thread(void *arg)
+{
+ char *sock_path = (char *)arg;
+ struct sockaddr_un addr;
+ int client_fd;
+ char buf[1024];
+
+ socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socket_fd < 0) {
+ FI_LOG("Failed to create socket, err = %s\n",
+ strerror(errno));
+ return NULL;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+
+ addr.sun_family = AF_UNIX;
+ g_strlcpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1);
+
+ unlink(sock_path);
+
+ if (bind(socket_fd, &addr, sizeof(addr)) < 0) {
+ FI_LOG("Failed to create socket, err = %s\n",
+ strerror(errno));
+ close(socket_fd);
+ return NULL;
+ }
+
+ if (listen(socket_fd, 1)) {
+ FI_LOG("Listen socket failed, err = %s\n",
+ strerror(errno));
+ close(socket_fd);
+ return NULL;
+ }
+
+ while (true) {
+ client_fd = accept(socket_fd, NULL, NULL);
+
+ if (client_fd < 0) {
+ if (plugin_is_shutting_down) {
+ break;
+ }
+ continue;
+ }
+
+ GString *xml_payload = g_string_new(NULL);
+
+ memset(buf, 0, sizeof(buf));
+
+ while (true) {
+ ssize_t bytes_read = read(client_fd, buf, sizeof(buf) - 1);
+
+ if (bytes_read > 0) {
+ g_string_append_len(xml_payload, buf, bytes_read);
+ } else if (bytes_read == 0) {
+ break;
+ } else {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (xml_payload->len > 0) {
+ GError *err = NULL;
+
+ GMarkupParseContext *ctx = g_markup_parse_context_new(&parser,
+ 0, NULL, NULL);
+
+ if (!g_markup_parse_context_parse(ctx, xml_payload->str,
+ xml_payload->len, &err)) {
+ FI_LOG("FI Error: Failed to parse dynamic XML: %s\n",
+ err->message);
+ g_error_free(err);
+ }
+
+ g_markup_parse_context_free(ctx);
+ }
+
+ g_string_free(xml_payload, TRUE);
+ close(client_fd);
+ }
+
+ unlink(sock_path);
+ g_free(sock_path);
+
+ return NULL;
+}
+
+static void plugin_exit_cb(qemu_plugin_id_t id, void *userdata)
+{
+ plugin_is_shutting_down = true;
+
+ if (socket_fd >= 0) {
+ close(socket_fd);
+ socket_fd = -1;
+ }
+}
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+ const qemu_info_t *info,
+ int argc, char **argv)
+{
+ const char *config_path = NULL;
+ const char *socket_path = NULL;
+ gchar *config;
+ gsize length;
+ GError *err = NULL;
+ bool success;
+
+ if (strcmp(info->target_name, "aarch64")) {
+ FI_LOG("FI: Target %s is not supported\n", info->target_name);
+ return 1;
+ }
+
+ for (int i = 0; i < argc; ++i) {
+ if (g_str_has_prefix(argv[i], "config=")) {
+ config_path = argv[i] + strlen("config=");
+ } else if (g_str_has_prefix(argv[i], "socket=")) {
+ socket_path = g_strdup(argv[i] + strlen("socket="));
+ }
+ }
+
+ if (!config_path && !socket_path) {
+ FI_LOG("FI: either config or socket path required\n");
+ return 1;
+ }
+
+ pc_faults = g_hash_table_new(g_int64_hash, g_int64_equal);
+ mem_faults = g_hash_table_new(g_int64_hash, g_int64_equal);
+ sys_reg_faults = g_hash_table_new(g_str_hash, g_str_equal);
+ mmio_override = g_hash_table_new(g_int64_hash, g_int64_equal);
+
+ g_rw_lock_init(&trigger_lock);
+ g_rw_lock_init(&mmio_override_lock);
+ g_rw_lock_init(&sysreg_lock);
+
+ if (config_path) {
+ if (access(config_path, R_OK)) {
+ FI_LOG("FI: can't access config file, err = %s\n",
+ strerror(errno));
+ return 1;
+ }
+
+ success = g_file_get_contents(config_path, &config,
+ &length, &err);
+ if (success) {
+ GMarkupParseContext *ctx = g_markup_parse_context_new(&parser,
+ 0, NULL, NULL);
+
+ success = g_markup_parse_context_parse(ctx, config, length, &err);
+ }
+
+ if (!success) {
+ FI_LOG("FI: failed to parse config file\n");
+ return 1;
+ }
+ }
+
+ if (socket_path) {
+ pthread_t thread_id;
+
+ pthread_create(&thread_id, NULL, ipc_listener_thread,
+ (void*)socket_path);
+ pthread_detach(thread_id);
+ }
+
+ qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb);
+ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans_cb);
+ qemu_plugin_register_mmio_override_cb(id, mmio_override_cb);
+
+ qemu_plugin_register_atexit_cb(id, plugin_exit_cb, NULL);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build
index 099319e7a1..df4d4c5177 100644
--- a/contrib/plugins/meson.build
+++ b/contrib/plugins/meson.build
@@ -12,6 +12,7 @@ contrib_plugins = [
'stoptrigger.c',
'traps.c',
'uftrace.c',
+'fault_injection.c',
]
if host_os != 'windows'
--
2.43.0
next prev parent reply other threads:[~2026-03-18 10:49 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-18 10:46 [RFC PATCH 0/9] plugins: Introduce Fault Injection framework and API extensions Ruslan Ruslichenko
2026-03-18 10:46 ` [RFC PATCH 1/9] target/arm: Add API for dynamic exception injection Ruslan Ruslichenko
2026-03-18 10:46 ` [RFC PATCH 2/9] plugins/api: Expose virtual clock timers to plugins Ruslan Ruslichenko
2026-03-18 10:46 ` [RFC PATCH 3/9] plugins: Expose Transaction Block cache flush API " Ruslan Ruslichenko
2026-03-18 10:46 ` [RFC PATCH 4/9] plugins: Introduce fault injection API and core subsystem Ruslan Ruslichenko
2026-03-18 10:46 ` [RFC PATCH 5/9] system/memory: Add plugin callbacks to intercept MMIO accesses Ruslan Ruslichenko
2026-03-18 10:46 ` [RFC PATCH 6/9] hw/intc/arm_gic: Register primary GIC for plugin IRQ injection Ruslan Ruslichenko
2026-03-18 10:46 ` [RFC PATCH 7/9] hw/arm/smmuv3: Add plugin fault handler for CMDQ errors Ruslan Ruslichenko
2026-03-18 10:46 ` Ruslan Ruslichenko [this message]
2026-03-18 10:46 ` [RFC PATCH 9/9] docs: Add description of fault-injection plugin and subsystem Ruslan Ruslichenko
2026-03-18 17:16 ` [RFC PATCH 0/9] plugins: Introduce Fault Injection framework and API extensions Pierrick Bouvier
2026-03-19 18:20 ` Ruslan Ruslichenko
2026-03-19 19:04 ` Pierrick Bouvier
2026-03-19 22:29 ` Ruslan Ruslichenko
2026-03-20 18:08 ` Pierrick Bouvier
2026-03-25 23:39 ` Ruslan Ruslichenko
2026-03-26 0:17 ` Pierrick Bouvier
2026-03-26 11:45 ` Alex Bennée
2026-03-26 15:59 ` Pierrick Bouvier
2026-03-27 18:18 ` Pierrick Bouvier
2026-03-31 20:23 ` Ruslan Ruslichenko
2026-03-31 21:24 ` Pierrick Bouvier
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260318104640.239752-9-ruslichenko.r@gmail.com \
--to=ruslichenko.r@gmail.com \
--cc=Ruslan_Ruslichenko@epam.com \
--cc=alex.bennee@linaro.org \
--cc=artem_mygaiev@epam.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@linaro.org \
--cc=pierrick.bouvier@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=volodymyr_babchuk@epam.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.