@@ -134,15 +134,16 @@ qemu_irq *qemu_irq_proxy(qemu_irq **target, int n)
return qemu_allocate_irqs(proxy_irq_handler, target, n);
}
-void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
+qemu_irq qemu_irq_dup(qemu_irq in)
{
- int i;
- qemu_irq *old_irqs = qemu_allocate_irqs(NULL, NULL, n);
- for (i = 0; i < n; i++) {
- *old_irqs[i] = *gpio_in[i];
- gpio_in[i]->handler = handler;
- gpio_in[i]->opaque = &old_irqs[i];
- }
+ qemu_irq out = qemu_allocate_irq(in->handler, in->opaque, in->n);
+ return out;
+}
+
+void qemu_irq_intercept_in(qemu_irq gpio_in, qemu_irq_handler handler, void *opaque)
+{
+ gpio_in->handler = handler;
+ gpio_in->opaque = opaque;
}
static const TypeInfo irq_type_info = {
@@ -58,8 +58,14 @@ qemu_irq qemu_irq_split(qemu_irq irq1, qemu_irq irq2);
*/
qemu_irq *qemu_irq_proxy(qemu_irq **target, int n);
-/* For internal use in qtest. Similar to qemu_irq_split, but operating
- on an existing vector of qemu_irq. */
-void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n);
+/**
+ * Duplicate an IRQ
+ * @param in the IRQ to deplicate
+ * @return a copy of the IRQ
+ */
+qemu_irq qemu_irq_dup(qemu_irq in);
+
+/* For internal use in qtest. */
+void qemu_irq_intercept_in(qemu_irq gpio_in, qemu_irq_handler handler, void *opaque);
#endif
@@ -40,7 +40,6 @@ static DeviceState *irq_intercept_dev;
static FILE *qtest_log_fp;
static CharBackend qtest_chr;
static GString *inbuf;
-static int irq_levels[MAX_IRQ];
static qemu_timeval start_time;
static bool qtest_opened;
@@ -160,10 +159,16 @@ static bool qtest_opened;
*
* IRQ raise NUM
* IRQ lower NUM
+ * IRQ_NAMED NAME NUM LEVEL
*
* where NUM is an IRQ number. For the PC, interrupts can be intercepted
* simply with "irq_intercept_in ioapic" (note that IRQ0 comes out with
* NUM=0 even though it is remapped to GSI 2).
+ *
+ * > irq_set NAME NUM LEVEL
+ * < OK
+ *
+ * Set the named input IRQ to the level (0/1)
*/
static int hex2nib(char ch)
@@ -243,17 +248,31 @@ static void GCC_FMT_ATTR(2, 3) qtest_sendf(CharBackend *chr,
va_end(ap);
}
+typedef struct qtest_irq {
+ qemu_irq old_irq;
+ char *name;
+ bool last_level;
+} qtest_irq;
+
static void qtest_irq_handler(void *opaque, int n, int level)
{
- qemu_irq old_irq = *(qemu_irq *)opaque;
- qemu_set_irq(old_irq, level);
+ qtest_irq *data = (qtest_irq *)opaque;
+ level = !!level;
+
+ qemu_set_irq(data->old_irq, level);
- if (irq_levels[n] != level) {
+ if (level != data->last_level) {
CharBackend *chr = &qtest_chr;
- irq_levels[n] = level;
qtest_send_prefix(chr);
- qtest_sendf(chr, "IRQ %s %d\n",
- level ? "raise" : "lower", n);
+
+ if (data->name) {
+ qtest_sendf(chr, "IRQ_NAMED %s %d %d\n",
+ data->name, n, level);
+ } else {
+ qtest_sendf(chr, "IRQ %s %d\n", level ? "raise" : "lower", n);
+ }
+
+ data->last_level = level;
}
}
@@ -289,7 +308,7 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
if (!dev) {
qtest_send_prefix(chr);
qtest_send(chr, "FAIL Unknown device\n");
- return;
+ return;
}
if (irq_intercept_dev) {
@@ -299,33 +318,73 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
} else {
qtest_send(chr, "OK\n");
}
- return;
+ return;
}
QLIST_FOREACH(ngl, &dev->gpios, node) {
- /* We don't support intercept of named GPIOs yet */
- if (ngl->name) {
- continue;
- }
if (words[0][14] == 'o') {
int i;
for (i = 0; i < ngl->num_out; ++i) {
- qemu_irq *disconnected = g_new0(qemu_irq, 1);
- qemu_irq icpt = qemu_allocate_irq(qtest_irq_handler,
- disconnected, i);
+ qtest_irq *data = g_new0(qtest_irq, 1);
+ data->name = ngl->name;
+ qemu_irq icpt = qemu_allocate_irq(qtest_irq_handler, data,
+ i);
- *disconnected = qdev_intercept_gpio_out(dev, icpt,
+ data->old_irq = qdev_intercept_gpio_out(dev, icpt,
ngl->name, i);
}
} else {
- qemu_irq_intercept_in(ngl->in, qtest_irq_handler,
- ngl->num_in);
+ int i;
+ for (i = 0; i < ngl->num_in; ++i) {
+ qtest_irq *data = g_new0(qtest_irq, 1);
+ data->name = ngl->name;
+ data->old_irq = qemu_irq_dup(ngl->in[i]);
+
+ qemu_irq_intercept_in(ngl->in[i], qtest_irq_handler, data);
+ }
}
}
irq_intercept_dev = dev;
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
+ } else if (strcmp(words[0], "irq_set") == 0) {
+ DeviceState *dev;
+ NamedGPIOList *ngl;
+ int level;
+ qemu_irq irq = NULL;
+ int irq_num;
+
+ g_assert(words[1]); /* device */
+ g_assert(words[2]); /* gpio list */
+ g_assert(words[3]); /* gpio line in list */
+ g_assert(words[4]); /* level */
+ dev = DEVICE(object_resolve_path(words[1], NULL));
+ if (!dev) {
+ qtest_send_prefix(chr);
+ qtest_send(chr, "FAIL Unknown device\n");
+ return;
+ }
+
+ irq_num = atoi(words[3]);
+ level = atoi(words[4]);
+
+ QLIST_FOREACH(ngl, &dev->gpios, node) {
+ if (strcmp(words[2], ngl->name) == 0 && ngl->num_in > irq_num) {
+ irq = ngl->in[irq_num];
+ }
+ }
+
+ if (irq == NULL) {
+ qtest_send_prefix(chr);
+ qtest_send(chr, "FAIL Unknown IRQ\n");
+ return;
+ }
+
+ qemu_set_irq(irq, level);
+
+ qtest_send_prefix(chr);
+ qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "outb") == 0 ||
strcmp(words[0], "outw") == 0 ||
strcmp(words[0], "outl") == 0) {
@@ -622,8 +681,6 @@ static int qtest_can_read(void *opaque)
static void qtest_event(void *opaque, int event)
{
- int i;
-
switch (event) {
case CHR_EVENT_OPENED:
/*
@@ -632,9 +689,6 @@ static void qtest_event(void *opaque, int event)
* used. Injects an extra reset even when it's not used, and
* that can mess up tests, e.g. -boot once.
*/
- for (i = 0; i < ARRAY_SIZE(irq_levels); i++) {
- irq_levels[i] = 0;
- }
qemu_gettimeofday(&start_time);
qtest_opened = true;
if (qtest_log_fp) {
@@ -21,12 +21,16 @@
#include <sys/wait.h>
#include <sys/un.h>
+#include <glib.h>
+
#include "qapi/qmp/json-parser.h"
#include "qapi/qmp/json-streamer.h"
#include "qapi/qmp/qjson.h"
+
#define MAX_IRQ 256
#define SOCKET_TIMEOUT 50
+#define IRQ_KEY_LENGTH 64
QTestState *global_qtest;
@@ -34,12 +38,22 @@ struct QTestState
{
int fd;
int qmp_fd;
+ GHashTable *irq_handlers;
bool irq_level[MAX_IRQ];
GString *rx;
pid_t qemu_pid; /* our child QEMU process */
bool big_endian;
};
+typedef struct irq_action {
+ void (*cb)(void *opaque, const char *name, int irq, bool level);
+ void *opaque;
+ const char *name;
+ int n;
+ bool level;
+} irq_action;
+
+
static GHookList abrt_hooks;
static GList *qtest_instances;
static struct sigaction sigact_old;
@@ -216,6 +230,8 @@ QTestState *qtest_init(const char *extra_args)
s->big_endian = qtest_query_target_endianness(s);
+ s->irq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
return s;
}
@@ -224,6 +240,8 @@ void qtest_quit(QTestState *s)
qtest_instances = g_list_remove(qtest_instances, s);
g_hook_destroy_link(&abrt_hooks, g_hook_find_data(&abrt_hooks, TRUE, s));
+ g_hash_table_destroy(s->irq_handlers);
+
/* Uninstall SIGABRT handler on last instance */
if (!qtest_instances) {
cleanup_sigabrt_handler();
@@ -304,11 +322,35 @@ static GString *qtest_recv_line(QTestState *s)
return line;
}
+void qtest_irq_attach(QTestState *s, const char *name, int irq,
+ void (*irq_cb)(void *opaque, const char *name, int irq, bool level),
+ void *opaque)
+{
+ char key[IRQ_KEY_LENGTH];
+ irq_action *action = g_new0(irq_action, 1);
+
+ action->cb = irq_cb;
+ action->name = name;
+ action->n = irq;
+ action->opaque = opaque;
+ action->level = false;
+
+ g_assert_cmpint(snprintf(key, sizeof(key), "%s.%d",
+ name, irq), <, sizeof(key));
+
+ g_hash_table_insert(s->irq_handlers, g_strdup(key), action);
+}
+
+#define MAX_ACTIONS 256
static gchar **qtest_rsp(QTestState *s, int expected_args)
{
GString *line;
gchar **words;
int i;
+ int action_index;
+ int action_count = 0;
+ bool action_raise[MAX_ACTIONS];
+ irq_action *actions[MAX_ACTIONS];
redo:
line = qtest_recv_line(s);
@@ -325,10 +367,29 @@ redo:
g_assert_cmpint(irq, >=, 0);
g_assert_cmpint(irq, <, MAX_IRQ);
- if (strcmp(words[1], "raise") == 0) {
- s->irq_level[irq] = true;
- } else {
- s->irq_level[irq] = false;
+ s->irq_level[irq] = (strcmp(words[1], "raise") == 0);
+
+ g_strfreev(words);
+ goto redo;
+ } else if (strcmp(words[0], "IRQ_NAMED") == 0) {
+ bool level;
+ char key[IRQ_KEY_LENGTH];
+ irq_action *action;
+
+ g_assert(words[1] != NULL);
+ g_assert(words[2] != NULL);
+ g_assert(words[3] != NULL);
+
+ level = (words[3][0] == '1');
+
+ g_assert_cmpint(snprintf(key, sizeof(key), "%s.%s",
+ words[1], words[2]), <, sizeof(key));
+
+ action = g_hash_table_lookup(s->irq_handlers, key);
+
+ if (action) {
+ action_raise[action_count] = level;
+ actions[action_count++] = action;
}
g_strfreev(words);
@@ -346,6 +407,17 @@ redo:
g_strfreev(words);
}
+/* Defer processing of IRQ actions until all communications have been handled,
+ * otherwise, interrupt handler that cause further communication can disrupt
+ * the communication stream
+ */
+ for (action_index = 0; action_index < action_count; action_index++) {
+ irq_action *action = actions[action_index];
+ action->cb(action->opaque, action->name, action->n,
+ action_raise[action_index]);
+ action->level = action_raise[action_index];
+ }
+
return words;
}
@@ -918,3 +990,10 @@ bool qtest_big_endian(QTestState *s)
{
return s->big_endian;
}
+
+void qtest_irq_set(QTestState *s, const char *id, const char *gpiolist, int n,
+ bool level)
+{
+ qtest_sendf(s, "irq_set %s %s %d %d\n", id, gpiolist, n, level);
+ qtest_rsp(s, 0);
+}
@@ -176,6 +176,34 @@ void qtest_irq_intercept_in(QTestState *s, const char *string);
void qtest_irq_intercept_out(QTestState *s, const char *string);
/**
+ * irq_attach:
+ * @s: #QTestState instance to operate on.
+ * @name: the name of the GPIO list containing the IRQ
+ * @irq: The IRQ number within the GPIO list to attach to
+ * @irq_cb: The callback to execute when the interrupt changes
+ * @opaque: opaque info to pass to the callback
+ *
+ * Attach a callback to an intercepted interrupt
+ */
+void qtest_irq_attach(QTestState *s, const char *name, int irq,
+ void (*irq_cb)(void *opaque, const char *name, int irq, bool level),
+ void *opaque);
+
+/**
+ * qtest_irq_set
+ * Set an interrupt level
+ * @s: #QTestState instance to operate on.
+ * @id: the device to inject interrupts for
+ * @gpiolist: the GPIO list containing the IRQ
+ * @n: the GPIO within the list
+ * @level: the IRQ level
+ *
+ * Set an interrupt to a nominated level
+ */
+void qtest_irq_set(QTestState *s, const char *id, const char *gpiolist, int n,
+ bool level);
+
+/**
* qtest_outb:
* @s: #QTestState instance to operate on.
* @addr: I/O port to write to.
@@ -626,6 +654,37 @@ static inline void irq_intercept_out(const char *string)
}
/**
+ * irq_attach:
+ * @name: the name of the gpio list containing the IRQ
+ * @irq: The IRQ to attach to
+ * @irq_cb: The callback to execute when the interrupt changes
+ * @opaque: opaque info to pass to the callback
+ *
+ * Attach a callback to an intecepted interrupt
+ */
+static inline void irq_attach(const char *name, int irq,
+ void (*irq_cb)(void *opaque, const char *name, int irq, bool level),
+ void *opaque)
+{
+ qtest_irq_attach(global_qtest, name, irq, irq_cb, opaque);
+}
+
+/**
+ * qtest_irq_set
+ * Set an interrupt level
+ * @id: the device to inject interrupts for
+ * @gpiolist: the GPIO list containing the line to seh
+ * @n: the line to set within the list
+ * @level: the IRQ level
+ */
+static inline void irq_set(const char *id, const char *gpiolist, int n,
+ bool level)
+{
+ qtest_irq_set(global_qtest, id, gpiolist, n, level);
+}
+
+
+/**
* outb:
* @addr: I/O port to write to.
* @value: Value being written.