]> git.proxmox.com Git - mirror_qemu.git/commitdiff
full TSS support - IO map check support - conforming segment check fixes - iret in...
authorbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>
Wed, 12 Nov 2003 23:39:19 +0000 (23:39 +0000)
committerbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>
Wed, 12 Nov 2003 23:39:19 +0000 (23:39 +0000)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@450 c046a42c-6fe2-441c-8c8c-71466251a162

target-i386/helper.c

index 1c0920bcf8d4b79e1080d8baccaead868a760bea..2f4a15d8735f16f5dad5a601a95ccfa7739f2ae0 100644 (file)
@@ -126,6 +126,56 @@ void cpu_loop_exit(void)
     longjmp(env->jmp_env, 1);
 }
 
+/* return non zero if error */
+static inline int load_segment(uint32_t *e1_ptr, uint32_t *e2_ptr,
+                               int selector)
+{
+    SegmentCache *dt;
+    int index;
+    uint8_t *ptr;
+
+    if (selector & 0x4)
+        dt = &env->ldt;
+    else
+        dt = &env->gdt;
+    index = selector & ~7;
+    if ((index + 7) > dt->limit)
+        return -1;
+    ptr = dt->base + index;
+    *e1_ptr = ldl_kernel(ptr);
+    *e2_ptr = ldl_kernel(ptr + 4);
+    return 0;
+}
+                                     
+static inline unsigned int get_seg_limit(uint32_t e1, uint32_t e2)
+{
+    unsigned int limit;
+    limit = (e1 & 0xffff) | (e2 & 0x000f0000);
+    if (e2 & DESC_G_MASK)
+        limit = (limit << 12) | 0xfff;
+    return limit;
+}
+
+static inline uint8_t *get_seg_base(uint32_t e1, uint32_t e2)
+{
+    return (uint8_t *)((e1 >> 16) | ((e2 & 0xff) << 16) | (e2 & 0xff000000));
+}
+
+static inline void load_seg_cache_raw_dt(SegmentCache *sc, uint32_t e1, uint32_t e2)
+{
+    sc->base = get_seg_base(e1, e2);
+    sc->limit = get_seg_limit(e1, e2);
+    sc->flags = e2;
+}
+
+/* init the segment cache in vm86 mode. */
+static inline void load_seg_vm(int seg, int selector)
+{
+    selector &= 0xffff;
+    cpu_x86_load_seg_cache(env, seg, selector, 
+                           (uint8_t *)(selector << 4), 0xffff, 0);
+}
+
 static inline void get_ss_esp_from_tss(uint32_t *ss_ptr, 
                                        uint32_t *esp_ptr, int dpl)
 {
@@ -161,54 +211,322 @@ static inline void get_ss_esp_from_tss(uint32_t *ss_ptr,
     }
 }
 
-/* return non zero if error */
-static inline int load_segment(uint32_t *e1_ptr, uint32_t *e2_ptr,
-                               int selector)
+/* XXX: merge with load_seg() */
+static void tss_load_seg(int seg_reg, int selector)
 {
+    uint32_t e1, e2;
+    int rpl, dpl, cpl;
+
+    if ((selector & 0xfffc) != 0) {
+        if (load_segment(&e1, &e2, selector) != 0)
+            raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+        if (!(e2 & DESC_S_MASK))
+            raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+        rpl = selector & 3;
+        dpl = (e2 >> DESC_DPL_SHIFT) & 3;
+        cpl = env->hflags & HF_CPL_MASK;
+        if (seg_reg == R_CS) {
+            if (!(e2 & DESC_CS_MASK))
+                raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+            if (dpl != rpl)
+                raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+            if ((e2 & DESC_C_MASK) && dpl > rpl)
+                raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+                
+        } else if (seg_reg == R_SS) {
+            /* SS must be writable data */
+            if ((e2 & DESC_CS_MASK) || !(e2 & DESC_W_MASK))
+                raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+            if (dpl != cpl || dpl != rpl)
+                raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+        } else {
+            /* not readable code */
+            if ((e2 & DESC_CS_MASK) && !(e2 & DESC_R_MASK))
+                raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+            /* if data or non conforming code, checks the rights */
+            if (((e2 >> DESC_TYPE_SHIFT) & 0xf) < 12) {
+                if (dpl < cpl || dpl < rpl)
+                    raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+            }
+        }
+        if (!(e2 & DESC_P_MASK))
+            raise_exception_err(EXCP0B_NOSEG, selector & 0xfffc);
+        cpu_x86_load_seg_cache(env, seg_reg, selector, 
+                       get_seg_base(e1, e2),
+                       get_seg_limit(e1, e2),
+                       e2);
+    } else {
+        if (seg_reg == R_SS || seg_reg == R_CS) 
+            raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
+    }
+}
+
+#define SWITCH_TSS_JMP  0
+#define SWITCH_TSS_IRET 1
+#define SWITCH_TSS_CALL 2
+
+/* XXX: restore CPU state in registers (PowerPC case) */
+static void switch_tss(int tss_selector, 
+                       uint32_t e1, uint32_t e2, int source)
+{
+    int tss_limit, tss_limit_max, type, old_tss_limit_max, old_type, v1, v2, i;
+    uint8_t *tss_base;
+    uint32_t new_regs[8], new_segs[6];
+    uint32_t new_eflags, new_eip, new_cr3, new_ldt, new_trap;
+    uint32_t old_eflags, eflags_mask;
     SegmentCache *dt;
     int index;
     uint8_t *ptr;
 
-    if (selector & 0x4)
-        dt = &env->ldt;
+    type = (e2 >> DESC_TYPE_SHIFT) & 0xf;
+
+    /* if task gate, we read the TSS segment and we load it */
+    if (type == 5) {
+        if (!(e2 & DESC_P_MASK))
+            raise_exception_err(EXCP0B_NOSEG, tss_selector & 0xfffc);
+        tss_selector = e1 >> 16;
+        if (tss_selector & 4)
+            raise_exception_err(EXCP0A_TSS, tss_selector & 0xfffc);
+        if (load_segment(&e1, &e2, tss_selector) != 0)
+            raise_exception_err(EXCP0D_GPF, tss_selector & 0xfffc);
+        if (e2 & DESC_S_MASK)
+            raise_exception_err(EXCP0D_GPF, tss_selector & 0xfffc);
+        type = (e2 >> DESC_TYPE_SHIFT) & 0xf;
+        if ((type & 7) != 1)
+            raise_exception_err(EXCP0D_GPF, tss_selector & 0xfffc);
+    }
+
+    if (!(e2 & DESC_P_MASK))
+        raise_exception_err(EXCP0B_NOSEG, tss_selector & 0xfffc);
+
+    if (type & 8)
+        tss_limit_max = 103;
     else
-        dt = &env->gdt;
-    index = selector & ~7;
+        tss_limit_max = 43;
+    tss_limit = get_seg_limit(e1, e2);
+    tss_base = get_seg_base(e1, e2);
+    if ((tss_selector & 4) != 0 || 
+        tss_limit < tss_limit_max)
+        raise_exception_err(EXCP0A_TSS, tss_selector & 0xfffc);
+    old_type = (env->tr.flags >> DESC_TYPE_SHIFT) & 0xf;
+    if (old_type & 8)
+        old_tss_limit_max = 103;
+    else
+        old_tss_limit_max = 43;
+
+    /* read all the registers from the new TSS */
+    if (type & 8) {
+        /* 32 bit */
+        new_cr3 = ldl_kernel(tss_base + 0x1c);
+        new_eip = ldl_kernel(tss_base + 0x20);
+        new_eflags = ldl_kernel(tss_base + 0x24);
+        for(i = 0; i < 8; i++)
+            new_regs[i] = ldl_kernel(tss_base + (0x28 + i * 4));
+        for(i = 0; i < 6; i++)
+            new_segs[i] = lduw_kernel(tss_base + (0x48 + i * 4));
+        new_ldt = lduw_kernel(tss_base + 0x60);
+        new_trap = ldl_kernel(tss_base + 0x64);
+    } else {
+        /* 16 bit */
+        new_cr3 = 0;
+        new_eip = lduw_kernel(tss_base + 0x0e);
+        new_eflags = lduw_kernel(tss_base + 0x10);
+        for(i = 0; i < 8; i++)
+            new_regs[i] = lduw_kernel(tss_base + (0x12 + i * 2)) | 0xffff0000;
+        for(i = 0; i < 4; i++)
+            new_segs[i] = lduw_kernel(tss_base + (0x22 + i * 4));
+        new_ldt = lduw_kernel(tss_base + 0x2a);
+        new_segs[R_FS] = 0;
+        new_segs[R_GS] = 0;
+        new_trap = 0;
+    }
+    
+    /* NOTE: we must avoid memory exceptions during the task switch,
+       so we make dummy accesses before */
+    /* XXX: it can still fail in some cases, so a bigger hack is
+       necessary to valid the TLB after having done the accesses */
+
+    v1 = ldub_kernel(env->tr.base);
+    v2 = ldub(env->tr.base + old_tss_limit_max);
+    stb_kernel(env->tr.base, v1);
+    stb_kernel(env->tr.base + old_tss_limit_max, v2);
+    
+    /* clear busy bit (it is restartable) */
+    if (source == SWITCH_TSS_JMP || source == SWITCH_TSS_IRET) {
+        uint8_t *ptr;
+        uint32_t e2;
+        ptr = env->gdt.base + (env->tr.selector << 3);
+        e2 = ldl_kernel(ptr + 4);
+        e2 &= ~DESC_TSS_BUSY_MASK;
+        stl_kernel(ptr + 4, e2);
+    }
+    old_eflags = compute_eflags();
+    if (source == SWITCH_TSS_IRET)
+        old_eflags &= ~NT_MASK;
+    
+    /* save the current state in the old TSS */
+    if (type & 8) {
+        /* 32 bit */
+        stl_kernel(env->tr.base + 0x20, env->eip);
+        stl_kernel(env->tr.base + 0x24, old_eflags);
+        for(i = 0; i < 8; i++)
+            stl_kernel(env->tr.base + (0x28 + i * 4), env->regs[i]);
+        for(i = 0; i < 6; i++)
+            stw_kernel(env->tr.base + (0x48 + i * 4), env->segs[i].selector);
+    } else {
+        /* 16 bit */
+        stw_kernel(env->tr.base + 0x0e, new_eip);
+        stw_kernel(env->tr.base + 0x10, old_eflags);
+        for(i = 0; i < 8; i++)
+            stw_kernel(env->tr.base + (0x12 + i * 2), env->regs[i]);
+        for(i = 0; i < 4; i++)
+            stw_kernel(env->tr.base + (0x22 + i * 4), env->segs[i].selector);
+    }
+    
+    /* now if an exception occurs, it will occurs in the next task
+       context */
+
+    if (source == SWITCH_TSS_CALL) {
+        stw_kernel(tss_base, env->tr.selector);
+        new_eflags |= NT_MASK;
+    }
+
+    /* set busy bit */
+    if (source == SWITCH_TSS_JMP || source == SWITCH_TSS_CALL) {
+        uint8_t *ptr;
+        uint32_t e2;
+        ptr = env->gdt.base + (tss_selector << 3);
+        e2 = ldl_kernel(ptr + 4);
+        e2 |= DESC_TSS_BUSY_MASK;
+        stl_kernel(ptr + 4, e2);
+    }
+
+    /* set the new CPU state */
+    /* from this point, any exception which occurs can give problems */
+    env->cr[0] |= CR0_TS_MASK;
+    env->tr.selector = tss_selector;
+    env->tr.base = tss_base;
+    env->tr.limit = tss_limit;
+    env->tr.flags = e2 & ~DESC_TSS_BUSY_MASK;
+    
+    if ((type & 8) && (env->cr[0] & CR0_PG_MASK)) {
+        env->cr[3] = new_cr3;
+        cpu_x86_update_cr3(env);
+    }
+    
+    /* load all registers without an exception, then reload them with
+       possible exception */
+    env->eip = new_eip;
+    eflags_mask = FL_UPDATE_CPL0_MASK;
+    if (!(type & 8))
+        eflags_mask &= 0xffff;
+    load_eflags(new_eflags, eflags_mask);
+    for(i = 0; i < 8; i++)
+        env->regs[i] = new_regs[i];
+    if (new_eflags & VM_MASK) {
+        for(i = 0; i < 6; i++) 
+            load_seg_vm(i, new_segs[i]);
+        /* in vm86, CPL is always 3 */
+        cpu_x86_set_cpl(env, 3);
+    } else {
+        /* CPL is set the RPL of CS */
+        cpu_x86_set_cpl(env, new_segs[R_CS] & 3);
+        /* first just selectors as the rest may trigger exceptions */
+        for(i = 0; i < 6; i++)
+            cpu_x86_load_seg_cache(env, i, new_segs[i], NULL, 0, 0);
+    }
+    
+    env->ldt.selector = new_ldt & ~4;
+    env->ldt.base = NULL;
+    env->ldt.limit = 0;
+    env->ldt.flags = 0;
+
+    /* load the LDT */
+    if (new_ldt & 4)
+        raise_exception_err(EXCP0A_TSS, new_ldt & 0xfffc);
+
+    dt = &env->gdt;
+    index = new_ldt & ~7;
     if ((index + 7) > dt->limit)
-        return -1;
+        raise_exception_err(EXCP0A_TSS, new_ldt & 0xfffc);
     ptr = dt->base + index;
-    *e1_ptr = ldl_kernel(ptr);
-    *e2_ptr = ldl_kernel(ptr + 4);
-    return 0;
+    e1 = ldl_kernel(ptr);
+    e2 = ldl_kernel(ptr + 4);
+    if ((e2 & DESC_S_MASK) || ((e2 >> DESC_TYPE_SHIFT) & 0xf) != 2)
+        raise_exception_err(EXCP0A_TSS, new_ldt & 0xfffc);
+    if (!(e2 & DESC_P_MASK))
+        raise_exception_err(EXCP0A_TSS, new_ldt & 0xfffc);
+    load_seg_cache_raw_dt(&env->ldt, e1, e2);
+    
+    /* load the segments */
+    if (!(new_eflags & VM_MASK)) {
+        tss_load_seg(R_CS, new_segs[R_CS]);
+        tss_load_seg(R_SS, new_segs[R_SS]);
+        tss_load_seg(R_ES, new_segs[R_ES]);
+        tss_load_seg(R_DS, new_segs[R_DS]);
+        tss_load_seg(R_FS, new_segs[R_FS]);
+        tss_load_seg(R_GS, new_segs[R_GS]);
+    }
+    
+    /* check that EIP is in the CS segment limits */
+    if (new_eip > env->segs[R_CS].limit) {
+        raise_exception_err(EXCP0D_GPF, 0);
+    }
 }
-                                     
-static inline unsigned int get_seg_limit(uint32_t e1, uint32_t e2)
+
+/* check if Port I/O is allowed in TSS */
+static inline void check_io(int addr, int size)
 {
-    unsigned int limit;
-    limit = (e1 & 0xffff) | (e2 & 0x000f0000);
-    if (e2 & DESC_G_MASK)
-        limit = (limit << 12) | 0xfff;
-    return limit;
+    int io_offset, val, mask;
+    
+    /* TSS must be a valid 32 bit one */
+    if (!(env->tr.flags & DESC_P_MASK) ||
+        ((env->tr.flags >> DESC_TYPE_SHIFT) & 0xf) != 9 ||
+        env->tr.limit < 103)
+        goto fail;
+    io_offset = lduw_kernel(env->tr.base + 0x66);
+    io_offset += (addr >> 3);
+    /* Note: the check needs two bytes */
+    if ((io_offset + 1) > env->tr.limit)
+        goto fail;
+    val = lduw_kernel(env->tr.base + io_offset);
+    val >>= (addr & 7);
+    mask = (1 << size) - 1;
+    /* all bits must be zero to allow the I/O */
+    if ((val & mask) != 0) {
+    fail:
+        raise_exception_err(EXCP0D_GPF, 0);
+    }
 }
 
-static inline uint8_t *get_seg_base(uint32_t e1, uint32_t e2)
+void check_iob_T0(void)
 {
-    return (uint8_t *)((e1 >> 16) | ((e2 & 0xff) << 16) | (e2 & 0xff000000));
+    check_io(T0, 1);
 }
 
-static inline void load_seg_cache_raw_dt(SegmentCache *sc, uint32_t e1, uint32_t e2)
+void check_iow_T0(void)
 {
-    sc->base = get_seg_base(e1, e2);
-    sc->limit = get_seg_limit(e1, e2);
-    sc->flags = e2;
+    check_io(T0, 2);
 }
 
-/* init the segment cache in vm86 mode. */
-static inline void load_seg_vm(int seg, int selector)
+void check_iol_T0(void)
 {
-    selector &= 0xffff;
-    cpu_x86_load_seg_cache(env, seg, selector, 
-                           (uint8_t *)(selector << 4), 0xffff, 0);
+    check_io(T0, 4);
+}
+
+void check_iob_DX(void)
+{
+    check_io(EDX & 0xffff, 1);
+}
+
+void check_iow_DX(void)
+{
+    check_io(EDX & 0xffff, 2);
+}
+
+void check_iol_DX(void)
+{
+    check_io(EDX & 0xffff, 4);
 }
 
 /* protected mode interrupt */
@@ -222,6 +540,21 @@ static void do_interrupt_protected(int intno, int is_int, int error_code,
     uint32_t e1, e2, offset, ss, esp, ss_e1, ss_e2, push_size;
     uint32_t old_cs, old_ss, old_esp, old_eip;
 
+    has_error_code = 0;
+    if (!is_int && !is_hw) {
+        switch(intno) {
+        case 8:
+        case 10:
+        case 11:
+        case 12:
+        case 13:
+        case 14:
+        case 17:
+            has_error_code = 1;
+            break;
+        }
+    }
+
     dt = &env->idt;
     if (intno * 8 + 7 > dt->limit)
         raise_exception_err(EXCP0D_GPF, intno * 8 + 2);
@@ -232,8 +565,27 @@ static void do_interrupt_protected(int intno, int is_int, int error_code,
     type = (e2 >> DESC_TYPE_SHIFT) & 0x1f;
     switch(type) {
     case 5: /* task gate */
-        cpu_abort(env, "task gate not supported");
-        break;
+        /* must do that check here to return the correct error code */
+        if (!(e2 & DESC_P_MASK))
+            raise_exception_err(EXCP0B_NOSEG, intno * 8 + 2);
+        switch_tss(intno * 8, e1, e2, SWITCH_TSS_CALL);
+        if (has_error_code) {
+            int mask;
+            /* push the error code */
+            shift = (env->segs[R_CS].flags >> DESC_B_SHIFT) & 1;
+            if (env->segs[R_SS].flags & DESC_B_MASK)
+                mask = 0xffffffff;
+            else
+                mask = 0xffff;
+            esp = (env->regs[R_ESP] - (2 << shift)) & mask;
+            ssp = env->segs[R_SS].base + esp;
+            if (shift)
+                stl_kernel(ssp, error_code);
+            else
+                stw_kernel(ssp, error_code);
+            env->regs[R_ESP] = (esp & mask) | (env->regs[R_ESP] & ~mask);
+        }
+        return;
     case 6: /* 286 interrupt gate */
     case 7: /* 286 trap gate */
     case 14: /* 386 interrupt gate */
@@ -293,20 +645,6 @@ static void do_interrupt_protected(int intno, int is_int, int error_code,
     }
 
     shift = type >> 3;
-    has_error_code = 0;
-    if (!is_int && !is_hw) {
-        switch(intno) {
-        case 8:
-        case 10:
-        case 11:
-        case 12:
-        case 13:
-        case 14:
-        case 17:
-            has_error_code = 1;
-            break;
-        }
-    }
     push_size = 6 + (new_stack << 2) + (has_error_code << 1);
     if (env->eflags & VM_MASK)
         push_size += 8;
@@ -688,7 +1026,7 @@ void helper_ltr_T0(void)
         e2 = ldl_kernel(ptr + 4);
         type = (e2 >> DESC_TYPE_SHIFT) & 0xf;
         if ((e2 & DESC_S_MASK) || 
-            (type != 2 && type != 9))
+            (type != 1 && type != 9))
             raise_exception_err(EXCP0D_GPF, selector & 0xfffc);
         if (!(e2 & DESC_P_MASK))
             raise_exception_err(EXCP0B_NOSEG, selector & 0xfffc);
@@ -701,6 +1039,7 @@ void helper_ltr_T0(void)
 
 /* only works if protected mode and not VM86. Calling load_seg with
    seg_reg == R_CS is discouraged */
+/* XXX: add ring level checks */
 void load_seg(int seg_reg, int selector, unsigned int cur_eip)
 {
     uint32_t e1, e2;
@@ -725,7 +1064,7 @@ void load_seg(int seg_reg, int selector, unsigned int cur_eip)
         }
 
         if (seg_reg == R_SS) {
-            if ((e2 & (DESC_CS_MASK | DESC_W_MASK)) == 0) {
+            if ((e2 & DESC_CS_MASK) || !(e2 & DESC_W_MASK)) {
                 EIP = cur_eip;
                 raise_exception_err(EXCP0D_GPF, selector & 0xfffc);
             }
@@ -757,7 +1096,7 @@ void load_seg(int seg_reg, int selector, unsigned int cur_eip)
 /* protected mode jump */
 void helper_ljmp_protected_T0_T1(void)
 {
-    int new_cs, new_eip;
+    int new_cs, new_eip, gate_cs, type;
     uint32_t e1, e2, cpl, dpl, rpl, limit;
 
     new_cs = T0;
@@ -771,7 +1110,7 @@ void helper_ljmp_protected_T0_T1(void)
         if (!(e2 & DESC_CS_MASK))
             raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
         dpl = (e2 >> DESC_DPL_SHIFT) & 3;
-        if (e2 & DESC_CS_MASK) {
+        if (e2 & DESC_C_MASK) {
             /* conforming code segment */
             if (dpl > cpl)
                 raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
@@ -792,8 +1131,52 @@ void helper_ljmp_protected_T0_T1(void)
                        get_seg_base(e1, e2), limit, e2);
         EIP = new_eip;
     } else {
-        cpu_abort(env, "jmp to call/task gate not supported 0x%04x:0x%08x", 
-                  new_cs, new_eip);
+        /* jump to call or task gate */
+        dpl = (e2 >> DESC_DPL_SHIFT) & 3;
+        rpl = new_cs & 3;
+        cpl = env->hflags & HF_CPL_MASK;
+        type = (e2 >> DESC_TYPE_SHIFT) & 0xf;
+        switch(type) {
+        case 1: /* 286 TSS */
+        case 9: /* 386 TSS */
+        case 5: /* task gate */
+            if (dpl < cpl || dpl < rpl)
+                raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
+            switch_tss(new_cs, e1, e2, SWITCH_TSS_JMP);
+            break;
+        case 4: /* 286 call gate */
+        case 12: /* 386 call gate */
+            if ((dpl < cpl) || (dpl < rpl))
+                raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
+            if (!(e2 & DESC_P_MASK))
+                raise_exception_err(EXCP0B_NOSEG, new_cs & 0xfffc);
+            gate_cs = e1 >> 16;
+            if (load_segment(&e1, &e2, gate_cs) != 0)
+                raise_exception_err(EXCP0D_GPF, gate_cs & 0xfffc);
+            dpl = (e2 >> DESC_DPL_SHIFT) & 3;
+            /* must be code segment */
+            if (((e2 & (DESC_S_MASK | DESC_CS_MASK)) != 
+                 (DESC_S_MASK | DESC_CS_MASK)))
+                raise_exception_err(EXCP0D_GPF, gate_cs & 0xfffc);
+            if (((e2 & DESC_C_MASK) && (dpl > cpl)) ||
+                (!(e2 & DESC_C_MASK) && (dpl != cpl)))
+                raise_exception_err(EXCP0D_GPF, gate_cs & 0xfffc);
+            if (!(e2 & DESC_P_MASK))
+                raise_exception_err(EXCP0D_GPF, gate_cs & 0xfffc);
+            new_eip = (e1 & 0xffff);
+            if (type == 12)
+                new_eip |= (e2 & 0xffff0000);
+            limit = get_seg_limit(e1, e2);
+            if (new_eip > limit)
+                raise_exception_err(EXCP0D_GPF, 0);
+            cpu_x86_load_seg_cache(env, R_CS, (gate_cs & 0xfffc) | cpl,
+                                   get_seg_base(e1, e2), limit, e2);
+            EIP = new_eip;
+            break;
+        default:
+            raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
+            break;
+        }
     }
 }
 
@@ -852,7 +1235,7 @@ void helper_lcall_protected_T0_T1(int shift, int next_eip)
         if (!(e2 & DESC_CS_MASK))
             raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
         dpl = (e2 >> DESC_DPL_SHIFT) & 3;
-        if (e2 & DESC_CS_MASK) {
+        if (e2 & DESC_C_MASK) {
             /* conforming code segment */
             if (dpl > cpl)
                 raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
@@ -898,11 +1281,15 @@ void helper_lcall_protected_T0_T1(int shift, int next_eip)
     } else {
         /* check gate type */
         type = (e2 >> DESC_TYPE_SHIFT) & 0x1f;
+        dpl = (e2 >> DESC_DPL_SHIFT) & 3;
+        rpl = new_cs & 3;
         switch(type) {
         case 1: /* available 286 TSS */
         case 9: /* available 386 TSS */
         case 5: /* task gate */
-            cpu_abort(env, "task gate not supported");
+            if (dpl < cpl || dpl < rpl)
+                raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
+            switch_tss(new_cs, e1, e2, SWITCH_TSS_CALL);
             break;
         case 4: /* 286 call gate */
         case 12: /* 386 call gate */
@@ -913,8 +1300,6 @@ void helper_lcall_protected_T0_T1(int shift, int next_eip)
         }
         shift = type >> 3;
 
-        dpl = (e2 >> DESC_DPL_SHIFT) & 3;
-        rpl = new_cs & 3;
         if (dpl < cpl || dpl < rpl)
             raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
         /* check valid bit */
@@ -1031,13 +1416,13 @@ void helper_lcall_protected_T0_T1(int shift, int next_eip)
     }
 }
 
-/* real mode iret */
+/* real and vm86 mode iret */
 void helper_iret_real(int shift)
 {
     uint32_t sp, new_cs, new_eip, new_eflags, new_esp;
     uint8_t *ssp;
     int eflags_mask;
-    
+
     sp = ESP & 0xffff;
     ssp = env->segs[R_SS].base + sp;
     if (shift == 1) {
@@ -1056,7 +1441,10 @@ void helper_iret_real(int shift)
         (new_esp & 0xffff);
     load_seg_vm(R_CS, new_cs);
     env->eip = new_eip;
-    eflags_mask = FL_UPDATE_CPL0_MASK;
+    if (env->eflags & VM_MASK)
+        eflags_mask = FL_UPDATE_MASK32 | IF_MASK | RF_MASK;
+    else
+        eflags_mask = FL_UPDATE_CPL0_MASK;
     if (shift == 0)
         eflags_mask &= 0xffff;
     load_eflags(new_eflags, eflags_mask);
@@ -1102,7 +1490,7 @@ static inline void helper_ret_protected(int shift, int is_iret, int addend)
     if (rpl < cpl)
         raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
     dpl = (e2 >> DESC_DPL_SHIFT) & 3;
-    if (e2 & DESC_CS_MASK) {
+    if (e2 & DESC_C_MASK) {
         if (dpl > rpl)
             raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
     } else {
@@ -1198,7 +1586,24 @@ static inline void helper_ret_protected(int shift, int is_iret, int addend)
 
 void helper_iret_protected(int shift)
 {
-    helper_ret_protected(shift, 1, 0);
+    int tss_selector, type;
+    uint32_t e1, e2;
+    
+    /* specific case for TSS */
+    if (env->eflags & NT_MASK) {
+        tss_selector = lduw_kernel(env->tr.base + 0);
+        if (tss_selector & 4)
+            raise_exception_err(EXCP0A_TSS, tss_selector & 0xfffc);
+        if (load_segment(&e1, &e2, tss_selector) != 0)
+            raise_exception_err(EXCP0A_TSS, tss_selector & 0xfffc);
+        type = (e2 >> DESC_TYPE_SHIFT) & 0x17;
+        /* NOTE: we check both segment and busy TSS */
+        if (type != 3)
+            raise_exception_err(EXCP0A_TSS, tss_selector & 0xfffc);
+        switch_tss(tss_selector, e1, e2, SWITCH_TSS_IRET);
+    } else {
+        helper_ret_protected(shift, 1, 0);
+    }
 }
 
 void helper_lret_protected(int shift, int addend)