diff --git a/lib/x86/desc.c b/lib/x86/desc.c
index 770c250..7c5c721 100644
--- a/lib/x86/desc.c
+++ b/lib/x86/desc.c
@@ -27,41 +27,6 @@ typedef struct {
 	u8 base_high;
 } gdt_entry_t;
 
-typedef struct {
-	u16 prev;
-	u16 res1;
-	u32 esp0;
-	u16 ss0;
-	u16 res2;
-	u32 esp1;
-	u16 ss1;
-	u16 res3;
-	u32 esp2;
-	u16 ss2;
-	u16 res4;
-	u32 cr3;
-	u32 eip;
-	u32 eflags;
-	u32 eax, ecx, edx, ebx, esp, ebp, esi, edi;
-	u16 es;
-	u16 res5;
-	u16 cs;
-	u16 res6;
-	u16 ss;
-	u16 res7;
-	u16 ds;
-	u16 res8;
-	u16 fs;
-	u16 res9;
-	u16 gs;
-	u16 res10;
-	u16 ldt;
-	u16 res11;
-	u16 t:1;
-	u16 res12:15;
-	u16 iomap_base;
-} tss32_t;
-
 extern idt_entry_t boot_idt[256];
 
 void set_idt_entry(int vec, void *addr, int dpl)
@@ -268,9 +233,11 @@ unsigned exception_error_code(void)
  * 0x18 - Not presend code segment
  * 0x20 - Primery task
  * 0x28 - Interrupt task
+ *
+ * 0x30 to 0x48 - Free to use for test cases
  */
 
-static gdt_entry_t gdt[6];
+static gdt_entry_t gdt[10];
 #define TSS_GDT_OFFSET 4
 
 void set_gdt_entry(int num, u32 base,  u32 limit, u8 access, u8 gran)
@@ -327,7 +294,7 @@ void setup_gdt(void)
 		      ".Lflush2: "::"r"(0x10));
 }
 
-static void set_idt_task_gate(int vec, u16 sel)
+void set_idt_task_gate(int vec, u16 sel)
 {
     idt_entry_t *e = &boot_idt[vec];
 
diff --git a/lib/x86/desc.h b/lib/x86/desc.h
index 0b4897c..f819452 100644
--- a/lib/x86/desc.h
+++ b/lib/x86/desc.h
@@ -24,6 +24,41 @@ struct ex_regs {
     unsigned long rflags;
 };
 
+typedef struct {
+	u16 prev;
+	u16 res1;
+	u32 esp0;
+	u16 ss0;
+	u16 res2;
+	u32 esp1;
+	u16 ss1;
+	u16 res3;
+	u32 esp2;
+	u16 ss2;
+	u16 res4;
+	u32 cr3;
+	u32 eip;
+	u32 eflags;
+	u32 eax, ecx, edx, ebx, esp, ebp, esi, edi;
+	u16 es;
+	u16 res5;
+	u16 cs;
+	u16 res6;
+	u16 ss;
+	u16 res7;
+	u16 ds;
+	u16 res8;
+	u16 fs;
+	u16 res9;
+	u16 gs;
+	u16 res10;
+	u16 ldt;
+	u16 res11;
+	u16 t:1;
+	u16 res12:15;
+	u16 iomap_base;
+} tss32_t;
+
 #define ASM_TRY(catch)                                  \
     "movl $0, %%gs:4 \n\t"                              \
     ".pushsection .data.ex \n\t"                        \
@@ -44,6 +79,7 @@ unsigned exception_error_code(void);
 void set_idt_entry(int vec, void *addr, int dpl);
 void set_idt_sel(int vec, u16 sel);
 void set_gdt_entry(int num, u32 base,  u32 limit, u8 access, u8 gran);
+void set_idt_task_gate(int vec, u16 sel);
 void set_intr_task_gate(int e, void *fn);
 void print_current_tss_info(void);
 void handle_exception(u8 v, void (*func)(struct ex_regs *regs));
diff --git a/lib/x86/vm.c b/lib/x86/vm.c
index 2852c6c..260ec45 100644
--- a/lib/x86/vm.c
+++ b/lib/x86/vm.c
@@ -109,14 +109,14 @@ void install_large_page(unsigned long *cr3,
                               unsigned long phys,
                               void *virt)
 {
-    install_pte(cr3, 2, virt, phys | PTE_PRESENT | PTE_WRITE | PTE_PSE, 0);
+    install_pte(cr3, 2, virt, phys | PTE_PRESENT | PTE_WRITE | PTE_USER | PTE_PSE, 0);
 }
 
 void install_page(unsigned long *cr3,
                   unsigned long phys,
                   void *virt)
 {
-    install_pte(cr3, 1, virt, phys | PTE_PRESENT | PTE_WRITE, 0);
+    install_pte(cr3, 1, virt, phys | PTE_PRESENT | PTE_WRITE | PTE_USER, 0);
 }
 
 
diff --git a/lib/x86/vm.h b/lib/x86/vm.h
index 3473f8d..0b5b5c7 100644
--- a/lib/x86/vm.h
+++ b/lib/x86/vm.h
@@ -13,6 +13,7 @@
 #define PTE_PRESENT (1ull << 0)
 #define PTE_PSE     (1ull << 7)
 #define PTE_WRITE   (1ull << 1)
+#define PTE_USER    (1ull << 2)
 #define PTE_ADDR    (0xffffffffff000ull)
 
 void setup_vm();
diff --git a/x86/taskswitch2.c b/x86/taskswitch2.c
index 683834e..6573696 100644
--- a/x86/taskswitch2.c
+++ b/x86/taskswitch2.c
@@ -5,6 +5,10 @@
 #include "processor.h"
 #include "vm.h"
 
+#define FREE_GDT_INDEX 6
+#define MAIN_TSS_INDEX (FREE_GDT_INDEX + 0)
+#define VM86_TSS_INDEX (FREE_GDT_INDEX + 1)
+
 #define xstr(s) str(s)
 #define str(s) #s
 
@@ -113,15 +117,10 @@ start:
 	goto start;
 }
 
-int main()
+void test_kernel_mode_int()
 {
 	unsigned int res;
 
-	setup_vm();
-	setup_idt();
-	setup_gdt();
-	setup_tss32();
-
 	/* test that int $2 triggers task gate */
 	test_count = 0;
 	set_intr_task_gate(2, nmi_tss);
@@ -217,6 +216,62 @@ int main()
 	asm volatile ("ljmp $" xstr(TSS_INTR) ", $0xf4f4f4f4");
 	printf("Jump back succeeded\n");
 	report("ljmp", test_count == 1);
+}
+
+void test_vm86_switch(void)
+{
+    static tss32_t main_tss;
+    static tss32_t vm86_tss;
+
+    u8 *vm86_start;
+
+    /* Write a 'ud2' instruction somewhere below 1 MB */
+    vm86_start = (void*) 0x42000;
+    vm86_start[0] = 0x0f;
+    vm86_start[1] = 0x0b;
+
+    /* Main TSS */
+    set_gdt_entry(MAIN_TSS_INDEX, (u32)&main_tss, sizeof(tss32_t) - 1, 0x89, 0);
+    ltr(MAIN_TSS_INDEX << 3);
+    main_tss = (tss32_t) {
+        .prev   = VM86_TSS_INDEX << 3,
+        .cr3    = read_cr3(),
+    };
+
+    /* VM86 TSS (marked as busy, so we can iret to it) */
+    set_gdt_entry(VM86_TSS_INDEX, (u32)&vm86_tss, sizeof(tss32_t) - 1, 0x8b, 0);
+    vm86_tss = (tss32_t) {
+        .eflags = 0x20002,
+        .cr3    = read_cr3(),
+        .eip    = (u32) vm86_start & 0x0f,
+        .cs     = (u32) vm86_start >> 4,
+        .ds     = 0x1234,
+        .es     = 0x2345,
+    };
+
+    /* Setup task gate to main TSS for #UD */
+    set_idt_task_gate(6, MAIN_TSS_INDEX << 3);
+
+    /* Jump into VM86 task with iret, #UD lets it come back immediately */
+    printf("Switch to VM86 task and back\n");
+    asm volatile(
+        "pushf\n"
+        "orw $0x4000, (%esp)\n"
+        "popf\n"
+        "iret\n"
+    );
+    report("VM86", 1);
+}
+
+int main()
+{
+	setup_vm();
+	setup_idt();
+	setup_gdt();
+	setup_tss32();
+
+	test_kernel_mode_int();
+	test_vm86_switch();
 
 	printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail);
 
