diff --git a/config/config-x86-common.mak b/config/config-x86-common.mak
index c2f9908..a7aa17a 100644
--- a/config/config-x86-common.mak
+++ b/config/config-x86-common.mak
@@ -7,6 +7,7 @@ cflatobjs += lib/x86/smp.o
 cflatobjs += lib/x86/vm.o
 cflatobjs += lib/x86/fwcfg.o
 cflatobjs += lib/x86/apic.o
+cflatobjs += lib/x86/pic.o
 cflatobjs += lib/x86/atomic.o
 cflatobjs += lib/x86/desc.o
 cflatobjs += lib/x86/isr.o
@@ -70,6 +71,8 @@ $(TEST_DIR)/apic.elf: $(cstart.o) $(TEST_DIR)/apic.o
 
 $(TEST_DIR)/ioapic.elf: $(cstart.o) $(TEST_DIR)/ioapic.o
 
+$(TEST_DIR)/pic.elf: $(cstart.o) $(TEST_DIR)/pic.o
+
 $(TEST_DIR)/tscdeadline_latency.elf: $(cstart.o) $(TEST_DIR)/tscdeadline_latency.o
 
 $(TEST_DIR)/init.elf: $(cstart.o) $(TEST_DIR)/init.o
diff --git a/config/config-x86_64.mak b/config/config-x86_64.mak
index 7d4eb34..0f2c47a 100644
--- a/config/config-x86_64.mak
+++ b/config/config-x86_64.mak
@@ -7,7 +7,7 @@ tests = $(TEST_DIR)/access.flat $(TEST_DIR)/apic.flat \
 	  $(TEST_DIR)/emulator.flat $(TEST_DIR)/idt_test.flat \
 	  $(TEST_DIR)/xsave.flat $(TEST_DIR)/rmap_chain.flat \
 	  $(TEST_DIR)/pcid.flat $(TEST_DIR)/debug.flat \
-	  $(TEST_DIR)/ioapic.flat
+	  $(TEST_DIR)/ioapic.flat $(TEST_DIR)/pic.flat
 tests += $(TEST_DIR)/svm.flat
 tests += $(TEST_DIR)/vmx.flat
 tests += $(TEST_DIR)/tscdeadline_latency.flat
diff --git a/lib/x86/apic.c b/lib/x86/apic.c
index 80b96d8..2e8e466 100644
--- a/lib/x86/apic.c
+++ b/lib/x86/apic.c
@@ -1,6 +1,8 @@
 #include "libcflat.h"
 #include "apic.h"
+#include "io.h"
 #include "msr.h"
+#include "pic.h"
 
 static void *g_apic = (void *)0xfee00000;
 static void *g_ioapic = (void *)0xfec00000;
@@ -12,9 +14,15 @@ struct apic_ops {
     u32 (*id)(void);
 };
 
-static void outb(unsigned char data, unsigned short port)
+void set_irq_line(unsigned line, int val)
 {
-    asm volatile ("out %0, %1" : : "a"(data), "d"(port));
+    outb(val, 0x2000 + line);
+}
+
+void toggle_irq_line(unsigned line)
+{
+    set_irq_line(line, 1);
+    set_irq_line(line, 0);
 }
 
 void eoi(void)
@@ -164,8 +172,10 @@ void enable_apic(void)
     xapic_write(0xf0, 0x1ff); /* spurious vector register */
 }
 
-void mask_pic_interrupts(void)
+void unmask_lvt0(void)
 {
-    outb(0xff, 0x21);
-    outb(0xff, 0xa1);
+    int lvt0 = apic_read(APIC_LVT0);
+    lvt0 &= ~APIC_LVT_MASKED;
+    lvt0 = SET_APIC_DELIVERY_MODE(lvt0, APIC_MODE_EXTINT);
+    apic_write(APIC_LVT0, lvt0);
 }
diff --git a/lib/x86/apic.h b/lib/x86/apic.h
index 216b98d..a5b19c0 100644
--- a/lib/x86/apic.h
+++ b/lib/x86/apic.h
@@ -20,6 +20,8 @@ typedef struct {
 
 void mask_pic_interrupts(void);
 
+void set_irq_line(unsigned line, int val);
+void toggle_irq_line(unsigned line);
 void eoi(void);
 
 void ioapic_write_redir(unsigned line, ioapic_redir_entry_t e);
@@ -37,4 +39,5 @@ uint32_t apic_id(void);
 
 int enable_x2apic(void);
 
+void unmask_lvt0(void);
 #endif
diff --git a/lib/x86/pic.c b/lib/x86/pic.c
new file mode 100644
index 0000000..e88d0b1
--- /dev/null
+++ b/lib/x86/pic.c
@@ -0,0 +1,80 @@
+#include "libcflat.h"
+#include "apic.h"
+#include "apic-defs.h"
+#include "io.h"
+#include "isr.h"
+#include "processor.h"
+
+#define PIC_MASTER 0x20
+#define PIC_SLAVE 0xA0
+#define PIC_MASTER_COMMAND PIC_MASTER
+#define PIC_MASTER_DATA (PIC_MASTER+1)
+#define PIC_SLAVE_COMMAND PIC_SLAVE
+#define PIC_SLAVE_DATA (PIC_SLAVE+1)
+
+#define PIC_IRQ_MASK 0x7
+#define PIC_NONSPECIFIC_EOI 0x20
+#define PIC_EOI 0x60
+
+#define PIC_MASTER_CASCADE_LINE 0x2
+
+unsigned char pic_read_data(int slave)
+{
+	unsigned char port = (slave) ? PIC_SLAVE_DATA : PIC_MASTER_DATA;
+
+	return inb(port);
+}
+
+void pic_write_data(unsigned char value, int slave)
+{
+	unsigned char port = (slave) ? PIC_SLAVE_DATA : PIC_MASTER_DATA;
+
+	outb(value, port);
+}
+
+unsigned char pic_read_command(int slave)
+{
+	unsigned char port = (slave) ? PIC_SLAVE_COMMAND : PIC_MASTER_COMMAND;
+
+	return inb(port);
+}
+
+void pic_write_command(unsigned char value, int slave)
+{
+	unsigned char port = (slave) ? PIC_SLAVE_COMMAND : PIC_MASTER_COMMAND;
+
+	outb(value, port);
+}
+
+void pic_eoi(unsigned char irq)
+{
+	if (irq >= 8) {
+		pic_write_command((irq & PIC_IRQ_MASK) | PIC_EOI, true);
+		pic_write_command(PIC_MASTER_CASCADE_LINE | PIC_EOI, false);
+	} else
+		pic_write_command(irq | PIC_EOI, false);
+}
+
+void pic_nonspecific_eoi(unsigned char irq)
+{
+	if (irq >= 8)
+		pic_write_command(PIC_NONSPECIFIC_EOI, true);
+
+	pic_write_command(PIC_NONSPECIFIC_EOI, false);
+}
+
+void set_pic_mask(unsigned char master, unsigned char slave)
+{
+	pic_write_data(master, false);
+	pic_write_data(slave, true);
+}
+
+void mask_pic_interrupts(void)
+{
+	set_pic_mask(0xff, 0xff);
+}
+
+void unmask_pic_interrupts(void)
+{
+	set_pic_mask(0x0, 0x0);
+}
diff --git a/lib/x86/pic.h b/lib/x86/pic.h
new file mode 100644
index 0000000..61e72ff
--- /dev/null
+++ b/lib/x86/pic.h
@@ -0,0 +1,20 @@
+#ifndef CFLAT_PIC_H
+#define CFLAT_PIC_H
+
+#define PIC_MASTER false
+#define PIC_SLAVE true
+
+void set_pic_mask(unsigned char master, unsigned char slave);
+void mask_pic_interrupts(void);
+void unmask_pic_interrupts(void);
+
+void pic_write_data(unsigned char value, int slave);
+unsigned char pic_read_data(int slave);
+
+void pic_write_command(unsigned char value, int slave);
+unsigned char pic_read_command(int slave);
+
+void pic_eoi(unsigned char irq);
+void pic_nonspecific_eoi(unsigned char irq);
+
+#endif  /* CFLAT_PIC_H */
diff --git a/x86/ioapic.c b/x86/ioapic.c
index 1fe1ccc..d91aa23 100644
--- a/x86/ioapic.c
+++ b/x86/ioapic.c
@@ -19,17 +19,6 @@ static void set_ioapic_redir(unsigned line, unsigned vec, unsigned trig_mode)
 	ioapic_write_redir(line, e);
 }
 
-static void set_irq_line(unsigned line, int val)
-{
-	asm volatile("out %0, %1" : : "a"((u8)val), "d"((u16)(0x2000 + line)));
-}
-
-static void toggle_irq_line(unsigned line)
-{
-	set_irq_line(line, 1);
-	set_irq_line(line, 0);
-}
-
 static void ioapic_reg_version(void)
 {
 	u8 version_offset;
diff --git a/x86/pic.c b/x86/pic.c
new file mode 100644
index 0000000..af6e291
--- /dev/null
+++ b/x86/pic.c
@@ -0,0 +1,330 @@
+#include "libcflat.h"
+#include "apic.h"
+#include "vm.h"
+#include "smp.h"
+#include "desc.h"
+#include "pic.h"
+#include "processor.h"
+#include "isr.h"
+
+#define MASTER_IRQ_BASE 0x08
+#define SLAVE_IRQ_BASE 0x70
+
+/* Uses GSI's 1 and 3 to avoid overlapping with the PIT and the cascade pin. */
+static volatile int g_master_pic_1;
+static volatile int g_master_pic_3;
+
+static void master_pic_1(isr_regs_t *regs)
+{
+	g_master_pic_1 = 1;
+	set_irq_line(0x01, 0x0);
+	pic_eoi(0x1);
+}
+
+static void master_pic_3(isr_regs_t *regs)
+{
+	g_master_pic_3 = 1;
+	set_irq_line(0x3, 0x0);
+	pic_eoi(0x3);
+}
+
+static void master_pic_autoeoi_1(isr_regs_t *regs)
+{
+	g_master_pic_1 = 1;
+	set_irq_line(0x01, 0x0);
+}
+
+static void master_pic_autoeoi_3(isr_regs_t *regs)
+{
+	g_master_pic_3 = 1;
+	set_irq_line(0x3, 0x0);
+}
+
+static volatile int g_slave_pic_0;
+static volatile int g_slave_pic_1;
+
+static void slave_pic_0(isr_regs_t *regs)
+{
+	g_slave_pic_0 = 1;
+	set_irq_line(0x8, 0x0);
+	pic_eoi(0x8);
+}
+
+static void slave_pic_1(isr_regs_t *regs)
+{
+	g_slave_pic_1 = 1;
+	set_irq_line(0x9, 0x0);
+	pic_eoi(0x9);
+}
+
+static void slave_pic_autoeoi_0(isr_regs_t *regs)
+{
+	g_slave_pic_0 = 1;
+	set_irq_line(0x8, 0x0);
+}
+
+static void slave_pic_autoeoi_1(isr_regs_t *regs)
+{
+	g_slave_pic_1 = 1;
+	set_irq_line(0x9, 0x0);
+}
+
+static void ignore_pit(isr_regs_t *regs)
+{
+	printf("PIT interrupt ignored\n");
+	set_irq_line(0x0, 0x0);
+	pic_eoi(0x0);
+}
+
+static void ignore_pit_autoeoi(isr_regs_t *regs)
+{
+	printf("PIT interrupt ignored\n");
+	set_irq_line(0x0, 0x0);
+}
+
+static void reset_test(void)
+{
+	g_master_pic_1 = 0;
+	g_master_pic_3 = 0;
+
+	g_slave_pic_0 = 0;
+	g_slave_pic_1 = 0;
+}
+
+static void install_default_handlers(void)
+{
+	handle_irq(MASTER_IRQ_BASE + 0x0, ignore_pit);
+
+	handle_irq(MASTER_IRQ_BASE + 0x1, master_pic_1);
+	handle_irq(MASTER_IRQ_BASE + 0x3, master_pic_3);
+
+	handle_irq(SLAVE_IRQ_BASE + 0x0, slave_pic_0);
+	handle_irq(SLAVE_IRQ_BASE + 0x1, slave_pic_1);
+}
+
+
+static void reset_pic_default(void)
+{
+	reset_test();
+	/* ICW1: reset, disable ICW4. */
+	pic_write_command(0x10, PIC_MASTER);
+	/* ICW2: Write base. */
+	pic_write_data(MASTER_IRQ_BASE, PIC_MASTER);
+	/* ICW3: Nothing. */
+	pic_write_data(0x0, PIC_MASTER);
+	/* ICW4: Skipped (could enable auto eoi, or fully nested). */
+
+	/* ICW1: reset, disable ICW4. */
+	pic_write_command(0x10, PIC_SLAVE);
+	/* ICW2: Write base. */
+	pic_write_data(SLAVE_IRQ_BASE, PIC_SLAVE);
+	/* ICW3: Nothing. */
+	pic_write_data(0x0, PIC_SLAVE);
+	/* ICW4: Skipped (could enable auto eoi, or fully nested). */
+}
+
+static void install_autoeoi_handlers(void)
+{
+	handle_irq(MASTER_IRQ_BASE + 0x0, ignore_pit_autoeoi);
+
+	handle_irq(MASTER_IRQ_BASE + 0x1, master_pic_autoeoi_1);
+	handle_irq(MASTER_IRQ_BASE + 0x3, master_pic_autoeoi_3);
+
+	handle_irq(SLAVE_IRQ_BASE + 0x0, slave_pic_autoeoi_0);
+	handle_irq(SLAVE_IRQ_BASE + 0x1, slave_pic_autoeoi_1);
+}
+
+static void reset_pic_autoeoi(void)
+{
+	reset_test();
+	/* ICW1: reset, enable ICW4. */
+	pic_write_command(0x11, PIC_MASTER);
+	/* ICW2: Write base. */
+	pic_write_data(MASTER_IRQ_BASE, PIC_MASTER);
+	/* ICW3: Nothing. */
+	pic_write_data(0x0, PIC_MASTER);
+	/* ICW4: Enable autoeoi. */
+	pic_write_data(0x2, PIC_MASTER);
+
+	/* ICW1: reset, enable ICW4. */
+	pic_write_command(0x11, PIC_SLAVE);
+	/* ICW2: Write base. */
+	pic_write_data(SLAVE_IRQ_BASE, PIC_SLAVE);
+	/* ICW3: Nothing. */
+	pic_write_data(0x0, PIC_SLAVE);
+	/* ICW4: Enable autoeoi. */
+	pic_write_data(0x2, PIC_SLAVE);
+}
+
+static void test_pic_mask(void)
+{
+	unsigned char slave, master;
+	unsigned char slave_initial = 0xef;
+	unsigned char master_initial = 0xfe;
+
+	set_pic_mask(master_initial, slave_initial);
+	master = pic_read_data(PIC_MASTER);
+	slave = pic_read_data(PIC_SLAVE);
+	report("PIC mask test",
+	       master == master_initial && slave == slave_initial);
+}
+
+static void test_pic_intr(void)
+{
+	set_pic_mask(0xfd, 0xff);
+	set_irq_line(0x1, 0x1);
+	asm volatile ("nop");
+	report("Master Interrupt", g_master_pic_1 == 1);
+}
+
+static void test_pic_masked_intr(void)
+{
+	set_pic_mask(0xff, 0xff);
+	set_irq_line(0x1, 0x1);
+	asm volatile ("nop");
+	report("Master Interrupt Masked", g_master_pic_1 == 0);
+	set_irq_line(0x1, 0x0);
+}
+
+static void test_pic_unmasked_intr(void)
+{
+	set_pic_mask(0x01, 0x00);
+	set_irq_line(0x1, 0x1);
+	asm volatile ("nop");
+	report("Unmasked Master Interrupt", g_master_pic_1 == 1);
+}
+
+static void test_pic_slave_intr(void)
+{
+	set_pic_mask(0xfb, 0xfe);
+	set_irq_line(0x8, 0x1);
+	asm volatile ("nop");
+	report("Slave interrupt", g_slave_pic_0 == 1);
+}
+
+static void test_pic_slave_masked_intr(void)
+{
+	set_pic_mask(0xff, 0xff);
+	set_irq_line(0x8, 0x1);
+	asm volatile ("nop");
+	report("Slave Interrupt Masked", g_slave_pic_0 == 0);
+	set_irq_line(0x8, 0x0);
+}
+
+static void test_pic_simul_intr(void)
+{
+	set_pic_mask(0xf5, 0xff);
+	set_irq_line(0x1, 0x1);
+	set_irq_line(0x3, 0x1);
+	asm volatile ("nop");
+	report("Simul Master interrupts",
+	       g_master_pic_1 == 1 && g_master_pic_3 == 1);
+}
+
+static void test_pic_unmask_simul_intr(void)
+{
+	mask_pic_interrupts();
+
+	set_irq_line(0x1, 0x1);
+	set_irq_line(0x3, 0x1);
+	set_pic_mask(0xf5, 0xff);
+	asm volatile ("nop");
+	report("Unmask two interrupts",
+	       g_master_pic_1 == 1 && g_master_pic_3 == 1);
+}
+
+static void test_pic_slave_simul_intr(void)
+{
+	set_pic_mask(0xfb, 0xfc);
+	set_irq_line(0x8, 0x1);
+	set_irq_line(0x9, 0x1);
+	asm volatile ("nop");
+	report("Simul Slave interrupts",
+	       g_slave_pic_0 == 1 && g_slave_pic_1 == 1);
+}
+
+static void test_pic_master_slave_simul_intr(void)
+{
+	set_pic_mask(0xf9, 0xfe);
+	set_irq_line(0x1, 0x1);
+	set_irq_line(0x8, 0x1);
+	asm volatile ("nop");
+	report("Simulataneous Master/Slave interrupts",
+	       g_slave_pic_0 == 1 && g_master_pic_1 == 1);
+}
+
+int main(void)
+{
+	setup_vm();
+	smp_init();
+	setup_idt();
+
+	install_default_handlers();
+
+	reset_pic_default();
+
+	test_pic_mask();
+
+	unmask_lvt0();
+	irq_enable();
+
+	/* Interrupt tests with default mode. */
+	reset_pic_default();
+	test_pic_intr();
+
+	reset_pic_default();
+	test_pic_masked_intr();
+
+	reset_pic_default();
+	test_pic_slave_masked_intr();
+
+	reset_pic_default();
+	test_pic_simul_intr();
+
+	reset_pic_default();
+	test_pic_unmask_simul_intr();
+
+	reset_pic_default();
+	test_pic_slave_intr();
+
+	reset_pic_default();
+	test_pic_slave_simul_intr();
+
+	reset_pic_default();
+	test_pic_master_slave_simul_intr();
+
+	reset_pic_default();
+	test_pic_unmasked_intr();
+
+	/* Interrupt tests with autoeoi mode. */
+	install_autoeoi_handlers();
+
+	reset_pic_autoeoi();
+	test_pic_intr();
+
+	reset_pic_autoeoi();
+	test_pic_masked_intr();
+
+	reset_pic_autoeoi();
+	test_pic_slave_masked_intr();
+
+	reset_pic_autoeoi();
+	test_pic_simul_intr();
+
+	reset_pic_autoeoi();
+	test_pic_unmask_simul_intr();
+
+	reset_pic_autoeoi();
+	test_pic_slave_intr();
+
+	reset_pic_autoeoi();
+	test_pic_slave_simul_intr();
+
+	reset_pic_autoeoi();
+	test_pic_master_slave_simul_intr();
+
+	reset_pic_autoeoi();
+	test_pic_unmasked_intr();
+
+	return report_summary();
+}
