diff --git a/x86/chaos.c b/x86/chaos.c
index 0b1e29c..8d421b5 100644
--- a/x86/chaos.c
+++ b/x86/chaos.c
@@ -8,9 +8,11 @@
 #include "alloc_page.h"
 #include "asm/page.h"
 #include "processor.h"
+#include "pci-edu.h"
 
 #define MAX_NR_CPUS 256
 
+#define EDU_MSI 0xc4
 #define TIMER_IRQ 0x44
 
 struct chaos_args {
@@ -20,21 +22,32 @@ struct chaos_args {
 
 	int hz;
 	bool hlt;
+
+	bool edu;
+	int edu_hz;
 };
 
 struct counters {
 	int ticks_left;
+	int edu_fraction;
+	int edu_msi;
 };
 
 int ncpus;
 struct chaos_args all_args[MAX_NR_CPUS];
 struct counters cnt[MAX_NR_CPUS];
 
+bool have_edu;
+struct pci_edu_dev edu_dev;
+
 static void parse_arg(struct chaos_args *args, const char *arg)
 {
 	char *s = strdup(arg);
 	char *p = s;
 
+	/* By default generate 17 MSIs per second (if enabled).  */
+	args->edu_hz = 17;
+
 	while (*p) {
 		char *word = p;
 		char delim = strdelim(&p, ",=");
@@ -84,10 +97,33 @@ static void parse_arg(struct chaos_args *args, const char *arg)
 			}
 			args->hlt = i;
 			printf("CPU %d: hlt=%ld\n", smp_id(), i);
+		} else if (!strcmp(word, "edu")) {
+			if (!have_arg)
+				i = 1;
+			else if (i != 0 && i != 1) {
+				printf("edu argument must be 0 or 1\n");
+				i = 1;
+			}
+			if (i != 0 && !have_edu) {
+				printf("edu device not found\n");
+				i = 0;
+			}
+			args->edu = i;
+			printf("CPU %d: edu=%ld\n", smp_id(), i);
+		} else if (!strcmp(word, "edu_hz")) {
+			if (!have_arg || !i)
+				i = 100;
+			args->edu_hz = i;
+			printf("CPU %d: edu_hz=%ld\n", smp_id(), i);
 		} else {
 			printf("invalid argument %s\n", word);
 		}
 	}
+	if (args->edu && args->edu_hz > args->hz) {
+		printf("MSI rate limited to the CPU's hz value\n");
+		args->edu_hz = args->hz;
+	}
+
 	free(s);
 }
 
@@ -97,12 +133,27 @@ static void do_timer(void)
 	struct counters *c = &cnt[cpu];
 	char out[4];
 	if (c->ticks_left > 0) {
+		/*
+		 * Bresenham algorithm, generate edu_hz MSIs interrupts
+		 * every hz timer ticks.  See the other half in the
+		 * stress function.
+		 */
+		if (all_args[cpu].edu)
+			c->edu_fraction += all_args[cpu].edu_hz;
+
 		c->ticks_left--;
 		return;
 	}
 
 	c->ticks_left = all_args[cpu].hz;
 
+	if (all_args[cpu].edu) {
+		if (!c->edu_msi) {
+			puts("!!! no MSI received for edu device\n");
+		}
+		c->edu_msi = 0;
+	}
+
 	/* Print current CPU number.  */
 	out[2] = (cpu % 10) + '0'; cpu /= 10;
 	out[1] = (cpu % 10) + '0'; cpu /= 10;
@@ -116,14 +167,33 @@ static void timer(isr_regs_t *regs)
         eoi();
 }
 
+static void edu(isr_regs_t *regs)
+{
+	int cpu = smp_id();
+	struct counters *c = &cnt[cpu];
+	c->edu_msi++;
+        eoi();
+}
+
+static void x86_setup_msi(struct pci_dev *pci_dev, int dest)
+{
+	u64 address = 0xFEE00000 + (dest << 12);
+	u32 data = EDU_MSI;
+	pci_setup_msi(pci_dev, address, data);
+}
+
 static void __attribute__((noreturn)) stress(void *data)
 {
     const char *arg = data;
     struct chaos_args *args = &all_args[smp_id()];
+    struct counters *c = &cnt[smp_id()];
 
     printf("starting CPU %d workload: %s\n", smp_id(), arg);
     parse_arg(args, arg);
 
+    /* Do not print errors the first time through.  */
+    c->edu_msi = 1;
+
     apic_write(APIC_TDCR, 0x0000000b);
     if (args->hz) {
 	    /* FIXME: assumes that the LAPIC timer counts in nanoseconds.  */
@@ -132,6 +202,11 @@ static void __attribute__((noreturn)) stress(void *data)
 	    apic_write(APIC_LVTT, TIMER_IRQ | APIC_LVT_TIMER_PERIODIC);
     }
 
+    if (args->edu) {
+	    printf("starting edu device\n");
+	    x86_setup_msi(&edu_dev.pci_dev, apic_id());
+    }
+
     irq_enable();
     for (;;) {
 	    if (args->mem) {
@@ -147,6 +222,13 @@ static void __attribute__((noreturn)) stress(void *data)
 	    }
 	    if (args->hlt)
 		    asm volatile("hlt");
+
+	    if (c->edu_fraction > args->hz) {
+		    c->edu_fraction -= args->hz;
+		    edu_reg_writel(&edu_dev, EDU_REG_INTR_RAISE, 1);
+		    while (!c->edu_msi)
+			    cpu_relax();
+	    }
     }
 }
 
@@ -159,12 +241,15 @@ int main(int argc, char *argv[])
         return 1;
     }
 
+    have_edu = edu_init(&edu_dev);
+
     argv++;
     argc--;
     ncpus = cpu_count();
     if (ncpus > MAX_NR_CPUS)
 	    ncpus = MAX_NR_CPUS;
 
+    handle_irq(EDU_MSI, edu);
     handle_irq(TIMER_IRQ, timer);
 
     for (i = 1; i < ncpus; ++i) {
