diff options
| author | 2011-06-17 07:01:45 +0000 | |
|---|---|---|
| committer | 2011-06-17 07:01:45 +0000 | |
| commit | 941c72c85abac3558f77509753db094012fcd2df (patch) | |
| tree | b7b292f6999b52454d1b80a0a5b897c39ef405fe /src | |
| parent | 3e22ec9a7a38e4121ed24bf62e355311317f44bd (diff) | |
i386: Implemented task gates, call gates, and task state segments (both
286 and 386). Also inplmented triple fault CPU resets (used by OS/2).
x87: Basic implementation of FSTENV (used by OS/2). [Barry Rodewald]
Diffstat (limited to 'src')
| -rw-r--r-- | src/emu/cpu/i386/i386.c | 1931 | ||||
| -rw-r--r-- | src/emu/cpu/i386/i386op16.c | 159 | ||||
| -rw-r--r-- | src/emu/cpu/i386/i386op32.c | 147 | ||||
| -rw-r--r-- | src/emu/cpu/i386/i386ops.c | 8 | ||||
| -rw-r--r-- | src/emu/cpu/i386/i386priv.h | 27 | ||||
| -rw-r--r-- | src/emu/cpu/i386/i486ops.c | 2 | ||||
| -rw-r--r-- | src/emu/cpu/i386/x87ops.c | 13 |
7 files changed, 2132 insertions, 155 deletions
diff --git a/src/emu/cpu/i386/i386.c b/src/emu/cpu/i386/i386.c index 39a515440fd..f990b7e18dd 100644 --- a/src/emu/cpu/i386/i386.c +++ b/src/emu/cpu/i386/i386.c @@ -23,6 +23,13 @@ int i386_parity_table[256]; MODRM_TABLE i386_MODRM_table[256]; +static void i386_trap_with_error(i386_state* cpustate, int irq, int irq_gate, int trap_level, UINT32 err); +static void i286_task_switch(i386_state* cpustate, UINT16 selector, UINT8 nested); +static void i386_task_switch(i386_state* cpustate, UINT16 selector, UINT8 nested); + +#define FAULT(fault,error) {i386_trap_with_error(cpustate,fault,0,0,error); return;} +#define FAULT_EXP(fault,error) {i386_trap_with_error(cpustate,fault,0,trap_level+1,error); return;} + /*************************************************************************/ #define INT_DEBUG 1 @@ -57,6 +64,38 @@ static void i386_load_protected_mode_segment(i386_state *cpustate, I386_SREG *se seg->d = (seg->flags & 0x4000) ? 1 : 0; } +static void i386_load_call_gate(i386_state* cpustate, I386_CALL_GATE *gate) +{ + UINT32 v1,v2; + UINT32 base,limit; + int entry; + + if ( gate->segment & 0x4 ) + { + base = cpustate->ldtr.base; + limit = cpustate->ldtr.limit; + } else { + base = cpustate->gdtr.base; + limit = cpustate->gdtr.limit; + } + + if (limit == 0 || gate->segment + 7 > limit) + return; + + entry = gate->segment & ~0x7; + + v1 = READ32(cpustate, base + entry ); + v2 = READ32(cpustate, base + entry + 4 ); + + /* Note that for task gates, offset and dword_count are not used */ + gate->selector = (v1 >> 16) & 0xffff; + gate->offset = (v1 & 0x0000ffff) | (v2 & 0xffff0000); + gate->ar = (v2 >> 8) & 0xff; + gate->dword_count = v2 & 0x001f; + gate->present = (gate->ar >> 7) & 0x01; + gate->dpl = (gate->ar >> 5) & 0x03; +} + static void i386_load_segment_descriptor(i386_state *cpustate, int segment ) { if (PROTECTED_MODE) @@ -79,6 +118,40 @@ static void i386_load_segment_descriptor(i386_state *cpustate, int segment ) } } +/* Retrieves the stack selector located in the current TSS */ +static UINT32 i386_get_stack_segment(i386_state* cpustate, UINT8 privilege) +{ + if(privilege >= 3) + return 0; + + return READ32(cpustate,(cpustate->task.base+8) + (8*privilege)); +} + +static UINT16 i286_get_stack_segment(i386_state* cpustate, UINT8 privilege) +{ + if(privilege >= 3) + return 0; + + return READ16(cpustate,(cpustate->task.base+4) + (4*privilege)); +} + +/* Retrieves the stack pointer located in the current TSS */ +static UINT32 i386_get_stack_ptr(i386_state* cpustate, UINT8 privilege) +{ + if(privilege >= 3) + return 0; + + return READ32(cpustate,(cpustate->task.base+4) + (8*privilege)); +} + +static UINT16 i286_get_stack_ptr(i386_state* cpustate, UINT8 privilege) +{ + if(privilege >= 3) + return 0; + + return READ16(cpustate,(cpustate->task.base+2) + (4*privilege)); +} + static UINT32 get_flags(i386_state *cpustate) { UINT32 f = 0x2; @@ -258,7 +331,55 @@ static UINT32 GetEA(i386_state *cpustate,UINT8 modrm) return i386_translate(cpustate, segment, ea ); } -static void i386_trap(i386_state *cpustate,int irq, int irq_gate) +/* Check segment register for validity when changing privilege level after an RETF */ +static void i386_check_sreg_validity(i386_state* cpustate, int reg) +{ + UINT16 selector = cpustate->sreg[reg].selector; + UINT8 CPL = cpustate->CPL; + UINT8 DPL,RPL; + I386_SREG desc; + int invalid = 0; + + memset(&desc, 0, sizeof(desc)); + desc.selector = selector; + i386_load_protected_mode_segment(cpustate,&desc); + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = selector & 0x03; + + /* Must be within the relevant descriptor table limits */ + if(selector & 0x04) + { + if((selector & ~0x07) > cpustate->ldtr.limit) + invalid = 1; + } + else + { + if((selector & ~0x07) > cpustate->gdtr.limit) + invalid = 1; + } + + /* Must be either a data or readable code segment */ + if(((desc.flags & 0x0018) == 0x0018 && (desc.flags & 0x0002)) || (desc.flags & 0x0018) == 0x0010) + invalid = 0; + else + invalid = 1; + + /* If a data segment or non-conforming code segment, then either DPL >= CPL or DPL >= RPL */ + if(((desc.flags & 0x0018) == 0x0018 && (desc.flags & 0x0004) == 0) || (desc.flags & 0x0018) == 0x0010) + { + if((DPL < CPL) || (DPL < RPL)) + invalid = 1; + } + + /* if segment is invalid, then segment register is nulled */ + if(invalid != 0) + { + cpustate->sreg[reg].selector = 0; + i386_load_segment_descriptor(cpustate,reg); + } +} + +static void i386_trap(i386_state *cpustate,int irq, int irq_gate, int trap_level) { /* I386 Interrupts/Traps/Faults: * @@ -284,11 +405,7 @@ static void i386_trap(i386_state *cpustate,int irq, int irq_gate) UINT32 offset; UINT16 segment; int entry = irq * (PROTECTED_MODE ? 8 : 4); - - /* Check if IRQ is out of IDTR's bounds */ - if( entry > cpustate->idtr.limit ) { - fatalerror("I386 Interrupt: IRQ out of IDTR bounds (IRQ: %d, IDTR Limit: %d)", irq, cpustate->idtr.limit); - } + int SetRPL = 0; if( !(PROTECTED_MODE) ) { @@ -303,26 +420,260 @@ static void i386_trap(i386_state *cpustate,int irq, int irq_gate) cpustate->sreg[CS].selector = READ16(cpustate, cpustate->idtr.base + entry + 2 ); cpustate->eip = READ16(cpustate, cpustate->idtr.base + entry ); - /* Interrupts that vector through either interrupt gates or trap gates cause TF */ - /* (the trap flag) to be reset after the current value of TF is saved on the stack as part of EFLAGS. */ cpustate->TF = 0; - - if (irq_gate) - { - cpustate->IF = 0; - } + cpustate->IF = 0; } else { int type; - /* 32-bit */ + UINT16 flags; + I386_SREG desc; + UINT8 CPL = 0, DPL = 0, RPL = 0; + I386_CALL_GATE gate; + /* 32-bit */ v1 = READ32(cpustate, cpustate->idtr.base + entry ); v2 = READ32(cpustate, cpustate->idtr.base + entry + 4 ); offset = (v2 & 0xffff0000) | (v1 & 0xffff); segment = (v1 >> 16) & 0xffff; type = (v2>>8) & 0x1F; + flags = (v2>>8) & 0xf0ff; + + if(trap_level == 2) + { + logerror("IRQ: Double fault.\n"); + FAULT_EXP(FAULT_DF,0); + } + if(trap_level >= 3) + { + logerror("IRQ: Triple fault. CPU reset.\n"); + device_set_input_line(cpustate->device, INPUT_LINE_RESET, PULSE_LINE); + return; + } + + // TODO: support for EXT bit + /* segment privilege checks */ + if(entry > cpustate->idtr.limit) + { + logerror("IRQ: Vector is past IDT limit.\n"); + FAULT_EXP(FAULT_GP,entry+2) + } + /* segment must be interrupt gate, trap gate, or task gate */ + if(type != 0x05 && type != 0x06 && type != 0x07 && type != 0x0e && type != 0x0f) + { + logerror("IRQ: Vector segment is not an interrupt, trap or task gate.\n"); + FAULT_EXP(FAULT_GP,entry+2) + } + + /* TODO: if software IRQ, then gate DPL must be less than CPL, else #GP(vector*8+2+EXT) */ + + if((flags & 0x0080) == 0) + { + logerror("IRQ: Vector segment is not present.\n"); + FAULT_EXP(FAULT_NP,entry+2) + } + + if(type == 0x05) + { + /* Task gate */ + memset(&gate, 0, sizeof(gate)); + gate.segment = segment; + i386_load_call_gate(cpustate,&gate); + memset(&desc, 0, sizeof(desc)); + desc.selector = gate.selector; + i386_load_protected_mode_segment(cpustate,&desc); + if(segment & 0x04) + { + logerror("IRQ: Task gate: TSS is not in the GDT.\n"); + FAULT_EXP(FAULT_TS,segment & ~0x07); + } + else + { + if(segment > cpustate->gdtr.limit) + { + logerror("IRQ: Task gate: TSS is past GDT limit.\n"); + FAULT_EXP(FAULT_TS,segment & ~0x07); + } + } + if((desc.flags & 0x000f) != 0x09 && (desc.flags & 0x000f) != 0x01) + { + logerror("IRQ: Task gate: TSS is not an available TSS.\n"); + FAULT_EXP(FAULT_TS,segment & ~0x07); + } + if((desc.flags & 0x0080) == 0) + { + logerror("IRQ: Task gate: TSS is not present.\n"); + FAULT_EXP(FAULT_NP,segment & ~0x07); + } + if(desc.flags & 0x08) + i386_task_switch(cpustate,desc.selector,0); + else + i286_task_switch(cpustate,desc.selector,0); + } + else + { + /* Interrupt or Trap gate */ + memset(&desc, 0, sizeof(desc)); + desc.selector = segment; + i386_load_protected_mode_segment(cpustate,&desc); + CPL = cpustate->CPL; // current privilege level + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = segment & 0x03; // requested privilege level + + if((segment & ~0x07) == 0) + { + logerror("IRQ: Gate segment is null.\n"); + FAULT_EXP(FAULT_GP,0) + } + if(segment & 0x04) + { + if((segment & ~0x07) > cpustate->ldtr.limit) + { + logerror("IRQ: Gate segment is past LDT limit.\n"); + FAULT_EXP(FAULT_GP,segment) + } + } + else + { + if((segment & ~0x07) > cpustate->gdtr.limit) + { + logerror("IRQ: Gate segment is past GDT limit.\n"); + FAULT_EXP(FAULT_GP,segment) + } + } + if((desc.flags & 0x0018) != 0x18) + { + logerror("IRQ: Gate descriptor is not a code segment.\n"); + FAULT_EXP(FAULT_GP,segment) + } + if((desc.flags & 0x0080) == 0) + { + logerror("IRQ: Gate segment is not present.\n"); + FAULT_EXP(FAULT_NP,segment) + } + if((desc.flags & 0x0004) == 0 && (DPL < CPL)) + { + /* IRQ to inner privilege */ + I386_SREG stack; + + /* Check new stack segment in TSS */ + memset(&stack, 0, sizeof(stack)); + stack.selector = i386_get_stack_segment(cpustate,DPL); + i386_load_protected_mode_segment(cpustate,&stack); + if((stack.selector & ~0x07) == 0) + { + logerror("IRQ: New stack selector is null.\n"); + FAULT_EXP(FAULT_GP,0) // #GP(EXT) + } + if(stack.selector & 0x04) + { + if((stack.selector & ~0x07) > cpustate->ldtr.base) + { + logerror("IRQ: New stack selector is past LDT limit.\n"); + FAULT_EXP(FAULT_TS,stack.selector & ~0x07) + } + } + else + { + if((stack.selector & ~0x07) > cpustate->gdtr.base) + { + logerror("IRQ: New stack selector is past GDT limit.\n"); + FAULT_EXP(FAULT_TS,stack.selector & ~0x07) // #TS(stack selector + EXT) + } + } + if((stack.selector & 0x03) != DPL) + { + logerror("IRQ: New stack selector RPL is not equal to code segment DPL.\n"); + FAULT_EXP(FAULT_TS,stack.selector & ~0x07) // #TS(stack selector + EXT) + } + if(((stack.flags >> 5) & 0x03) != DPL) + { + logerror("IRQ: New stack segment DPL is not equal to code segment DPL.\n"); + FAULT_EXP(FAULT_TS,stack.selector & ~0x07) // #TS(stack selector + EXT) + } + if(((stack.flags & 0x0018) != 0x10) && (stack.flags & 0x0002) != 0) + { + logerror("IRQ: New stack segment is not a writable data segment.\n"); + FAULT_EXP(FAULT_TS,stack.selector & ~0x07) // #TS(stack selector + EXT) + } + if((stack.flags & 0x0080) == 0) + { + logerror("IRQ: New stack segment is not present.\n"); + FAULT_EXP(FAULT_SS,stack.selector & ~0x07) // #TS(stack selector + EXT) + } + if(type & 0x08) // 32-bit gate + { + UINT32 newESP = i386_get_stack_ptr(cpustate,DPL); + if(newESP < 20) + { + logerror("IRQ: New stack has no space for return addresses.\n"); + FAULT_EXP(FAULT_SS,0) + } + } + else // 16-bit gate + { + UINT32 newESP = i386_get_stack_ptr(cpustate,DPL); + if(newESP < 10) + { + logerror("IRQ: New stack has no space for return addresses.\n"); + FAULT_EXP(FAULT_SS,0) + } + } + if(offset > desc.limit) + { + logerror("IRQ: New EIP is past code segment limit.\n"); + FAULT_EXP(FAULT_GP,0) + } + /* Load new stack segment descriptor */ + cpustate->sreg[SS].selector = i386_get_stack_segment(cpustate,DPL); + i386_load_segment_descriptor(cpustate,SS); + REG32(ESP) = i386_get_stack_ptr(cpustate,DPL); + if(type & 0x08) + { + // 32-bit gate + PUSH32(cpustate,cpustate->sreg[SS].selector); + PUSH32(cpustate,REG32(ESP)); + } + else + { + // 16-bit gate + PUSH16(cpustate,cpustate->sreg[SS].selector); + PUSH16(cpustate,REG16(ESP)); + } + cpustate->CPL = DPL; + SetRPL = 1; + } + else + { + int stack_limit; + if((desc.flags & 0x0004) || (DPL == CPL)) + { + /* IRQ to same privilege */ + if(type == 0x0e || type == 0x0f) // 32-bit gate + stack_limit = 10; + else + stack_limit = 6; + // TODO: Add check for error code (2 extra bytes) + if(REG32(ESP) < stack_limit) + { + logerror("IRQ: Stack has no space left (needs %i bytes).\n",stack_limit); + FAULT_EXP(FAULT_SS,0) + } + if(offset > desc.limit) + { + logerror("IRQ: Gate segment offset is past segment limit.\n"); + FAULT_EXP(FAULT_GP,0) + } + SetRPL = 1; + } + else + { + logerror("IRQ: Gate descriptor is non-conforming, and DPL does not equal CPL.\n"); + FAULT_EXP(FAULT_GP,segment) + } + } + } if(type != 0x0e && type != 0x0f) // if not 386 interrupt or trap gate { @@ -342,25 +693,251 @@ static void i386_trap(i386_state *cpustate,int irq, int irq_gate) else PUSH32(cpustate, cpustate->prev_eip ); } - + if(SetRPL != 0) + segment = (segment & ~0x03) | cpustate->CPL; cpustate->sreg[CS].selector = segment; cpustate->eip = offset; - /* Interrupts that vector through either interrupt gates or trap gates cause TF */ - /* (the trap flag) to be reset after the current value of TF is saved on the stack as part of EFLAGS. */ - if ((type == 14) || (type==15)) - cpustate->TF = 0; + if(type == 0x0e || type == 0x06) + cpustate->IF = 0; + cpustate->TF = 0; + cpustate->NT = 0; + } + + i386_load_segment_descriptor(cpustate,CS); + CHANGE_PC(cpustate,cpustate->eip); - if (type == 14) +} + +static void i386_trap_with_error(i386_state *cpustate,int irq, int irq_gate, int trap_level, UINT32 error) +{ + i386_trap(cpustate,irq,irq_gate,trap_level); + if(irq == 8 || irq == 10 || irq == 11 || irq == 12 || irq == 13 || irq == 14) + { + // for these exceptions, an error code is pushed onto the stack by the processor. + // no error code is pushed for software interrupts, either. + PUSH32(cpustate,error); + } +} + + +static void i286_task_switch(i386_state *cpustate, UINT16 selector, UINT8 nested) +{ + UINT32 tss; + I386_SREG seg; + UINT16 old_task; + UINT8 ar_byte; // access rights byte + + /* TODO: Task State Segment privilege checks */ + + /* For tasks that aren't nested, clear the busy bit in the task's descriptor */ + if(nested == 0) + { + if(cpustate->sreg[CS].selector & 0x0004) { - cpustate->IF = 0; + ar_byte = READ8(cpustate,cpustate->ldtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->ldtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5,ar_byte & ~0x02); } + else + { + ar_byte = READ8(cpustate,cpustate->gdtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->gdtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5,ar_byte & ~0x02); + } + } + /* Save the state of the current task in the current TSS (TR register base) */ + tss = cpustate->task.base; + WRITE16(cpustate,tss+0x0e,cpustate->eip & 0x0000ffff); + WRITE16(cpustate,tss+0x10,get_flags(cpustate) & 0x0000ffff); + WRITE16(cpustate,tss+0x12,REG16(AX)); + WRITE16(cpustate,tss+0x14,REG16(CX)); + WRITE16(cpustate,tss+0x16,REG16(DX)); + WRITE16(cpustate,tss+0x18,REG16(BX)); + WRITE16(cpustate,tss+0x1a,REG16(SP)); + WRITE16(cpustate,tss+0x1c,REG16(BP)); + WRITE16(cpustate,tss+0x1e,REG16(SI)); + WRITE16(cpustate,tss+0x20,REG16(DI)); + WRITE16(cpustate,tss+0x22,cpustate->sreg[ES].selector); + WRITE16(cpustate,tss+0x24,cpustate->sreg[CS].selector); + WRITE16(cpustate,tss+0x26,cpustate->sreg[SS].selector); + WRITE16(cpustate,tss+0x28,cpustate->sreg[DS].selector); + + old_task = cpustate->task.segment; + + /* Load task register with the selector of the incoming task */ + cpustate->task.segment = selector; + memset(&seg, 0, sizeof(seg)); + seg.selector = cpustate->task.segment; + i386_load_protected_mode_segment(cpustate,&seg); + cpustate->task.limit = seg.limit; + cpustate->task.base = seg.base; + cpustate->task.flags = seg.flags; + + /* Set TS bit in CR0 */ + cpustate->cr[0] |= 0x08; + + /* Load incoming task state from the new task's TSS */ + tss = cpustate->task.base; + cpustate->eip = READ16(cpustate,tss+0x0e); + set_flags(cpustate,READ32(cpustate,tss+0x10)); + REG16(AX) = READ16(cpustate,tss+0x12); + REG16(CX) = READ16(cpustate,tss+0x14); + REG16(DX) = READ16(cpustate,tss+0x16); + REG16(BX) = READ16(cpustate,tss+0x18); + REG16(SP) = READ16(cpustate,tss+0x1a); + REG16(BP) = READ16(cpustate,tss+0x1c); + REG16(SI) = READ16(cpustate,tss+0x1e); + REG16(DI) = READ16(cpustate,tss+0x20); + cpustate->sreg[ES].selector = READ16(cpustate,tss+0x22) & 0xffff; + i386_load_segment_descriptor(cpustate, ES); + cpustate->sreg[CS].selector = READ16(cpustate,tss+0x24) & 0xffff; + i386_load_segment_descriptor(cpustate, CS); + cpustate->sreg[SS].selector = READ16(cpustate,tss+0x26) & 0xffff; + i386_load_segment_descriptor(cpustate, SS); + cpustate->sreg[DS].selector = READ16(cpustate,tss+0x28) & 0xffff; + i386_load_segment_descriptor(cpustate, DS); + cpustate->ldtr.segment = READ32(cpustate,tss+0x2a) & 0xffff; + seg.selector = cpustate->ldtr.segment; + i386_load_protected_mode_segment(cpustate,&seg); + cpustate->ldtr.limit = seg.limit; + cpustate->ldtr.base = seg.base; + cpustate->ldtr.flags = seg.flags; + + /* Set the busy bit in the new task's descriptor */ + if(seg.selector & 0x0004) + { + ar_byte = READ8(cpustate,cpustate->ldtr.base + ((seg.selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->ldtr.base + ((seg.selector & ~0x0007)*8) + 5,ar_byte | 0x02); + } + else + { + ar_byte = READ8(cpustate,cpustate->gdtr.base + ((seg.selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->gdtr.base + ((seg.selector & ~0x0007)*8) + 5,ar_byte | 0x02); } - i386_load_segment_descriptor(cpustate,CS); - CHANGE_PC(cpustate,cpustate->eip); + /* For nested tasks, we write the outgoing task's selector to the back-link field of the new TSS, + and set the NT flag in the EFLAGS register */ + if(nested != 0) + { + WRITE16(cpustate,tss+0,old_task); + cpustate->NT = 1; + } + + cpustate->CPL = cpustate->sreg[CS].selector & 0x03; + printf("286 Task Switch from selector %04x to %04x\n",old_task,seg.selector); +} + +static void i386_task_switch(i386_state *cpustate, UINT16 selector, UINT8 nested) +{ + UINT32 tss; + I386_SREG seg; + UINT16 old_task; + UINT8 ar_byte; // access rights byte + + /* TODO: Task State Segment privilege checks */ + + /* For tasks that aren't nested, clear the busy bit in the task's descriptor */ + if(nested == 0) + { + if(cpustate->sreg[CS].selector & 0x0004) + { + ar_byte = READ8(cpustate,cpustate->ldtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->ldtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5,ar_byte & ~0x02); + } + else + { + ar_byte = READ8(cpustate,cpustate->gdtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->gdtr.base + ((cpustate->sreg[CS].selector & ~0x0007)*8) + 5,ar_byte & ~0x02); + } + } + /* Save the state of the current task in the current TSS (TR register base) */ + tss = cpustate->task.base; + WRITE32(cpustate,tss+0x20,cpustate->eip); + WRITE32(cpustate,tss+0x24,get_flags(cpustate)); + WRITE32(cpustate,tss+0x28,REG32(EAX)); + WRITE32(cpustate,tss+0x2c,REG32(ECX)); + WRITE32(cpustate,tss+0x30,REG32(EDX)); + WRITE32(cpustate,tss+0x34,REG32(EBX)); + WRITE32(cpustate,tss+0x38,REG32(ESP)); + WRITE32(cpustate,tss+0x3c,REG32(EBP)); + WRITE32(cpustate,tss+0x40,REG32(ESI)); + WRITE32(cpustate,tss+0x44,REG32(EDI)); + WRITE32(cpustate,tss+0x48,cpustate->sreg[ES].selector); + WRITE32(cpustate,tss+0x4c,cpustate->sreg[CS].selector); + WRITE32(cpustate,tss+0x50,cpustate->sreg[SS].selector); + WRITE32(cpustate,tss+0x54,cpustate->sreg[DS].selector); + WRITE32(cpustate,tss+0x58,cpustate->sreg[FS].selector); + WRITE32(cpustate,tss+0x5c,cpustate->sreg[GS].selector); + + old_task = cpustate->task.segment; + + /* Load task register with the selector of the incoming task */ + cpustate->task.segment = selector; + memset(&seg, 0, sizeof(seg)); + seg.selector = cpustate->task.segment; + i386_load_protected_mode_segment(cpustate,&seg); + cpustate->task.limit = seg.limit; + cpustate->task.base = seg.base; + cpustate->task.flags = seg.flags; + + /* Set TS bit in CR0 */ + cpustate->cr[0] |= 0x08; + + /* Load incoming task state from the new task's TSS */ + tss = cpustate->task.base; + cpustate->cr[3] = READ32(cpustate,tss+0x1c); // CR3 (PDBR) + cpustate->eip = READ32(cpustate,tss+0x20); + set_flags(cpustate,READ32(cpustate,tss+0x24)); + REG32(EAX) = READ32(cpustate,tss+0x28); + REG32(ECX) = READ32(cpustate,tss+0x2c); + REG32(EDX) = READ32(cpustate,tss+0x30); + REG32(EBX) = READ32(cpustate,tss+0x34); + REG32(ESP) = READ32(cpustate,tss+0x38); + REG32(EBP) = READ32(cpustate,tss+0x3c); + REG32(ESI) = READ32(cpustate,tss+0x40); + REG32(EDI) = READ32(cpustate,tss+0x44); + cpustate->sreg[ES].selector = READ32(cpustate,tss+0x48) & 0xffff; + i386_load_segment_descriptor(cpustate, ES); + cpustate->sreg[CS].selector = READ32(cpustate,tss+0x4c) & 0xffff; + i386_load_segment_descriptor(cpustate, CS); + cpustate->sreg[SS].selector = READ32(cpustate,tss+0x50) & 0xffff; + i386_load_segment_descriptor(cpustate, SS); + cpustate->sreg[DS].selector = READ32(cpustate,tss+0x54) & 0xffff; + i386_load_segment_descriptor(cpustate, DS); + cpustate->sreg[FS].selector = READ32(cpustate,tss+0x58) & 0xffff; + i386_load_segment_descriptor(cpustate, FS); + cpustate->sreg[GS].selector = READ32(cpustate,tss+0x5c) & 0xffff; + i386_load_segment_descriptor(cpustate, GS); + cpustate->ldtr.segment = READ32(cpustate,tss+0x60) & 0xffff; + seg.selector = cpustate->ldtr.segment; + i386_load_protected_mode_segment(cpustate,&seg); + cpustate->ldtr.limit = seg.limit; + cpustate->ldtr.base = seg.base; + cpustate->ldtr.flags = seg.flags; + + /* Set the busy bit in the new task's descriptor */ + if(seg.selector & 0x0004) + { + ar_byte = READ8(cpustate,cpustate->ldtr.base + ((seg.selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->ldtr.base + ((seg.selector & ~0x0007)*8) + 5,ar_byte | 0x02); + } + else + { + ar_byte = READ8(cpustate,cpustate->gdtr.base + ((seg.selector & ~0x0007)*8) + 5); + WRITE8(cpustate,cpustate->gdtr.base + ((seg.selector & ~0x0007)*8) + 5,ar_byte | 0x02); + } + + /* For nested tasks, we write the outgoing task's selector to the back-link field of the new TSS, + and set the NT flag in the EFLAGS register */ + if(nested != 0) + { + WRITE32(cpustate,tss+0,old_task); + cpustate->NT = 1; + } + + cpustate->CPL = cpustate->sreg[CS].selector & 0x03; + printf("386 Task Switch from selector %04x to %04x",old_task,seg.selector); } static void i386_check_irq_line(i386_state *cpustate) @@ -369,8 +946,1309 @@ static void i386_check_irq_line(i386_state *cpustate) if ( (cpustate->irq_state) && cpustate->IF ) { cpustate->cycles -= 2; - i386_trap(cpustate,cpustate->irq_callback(cpustate->device, 0), 1); + i386_trap(cpustate,cpustate->irq_callback(cpustate->device, 0), 1, 0); + } +} + +static void i386_protected_mode_jump(i386_state *cpustate, UINT16 seg, UINT32 off, int indirect, int operand32) +{ + I386_SREG desc; + I386_CALL_GATE call_gate; + UINT8 CPL,DPL,RPL; + UINT8 SetRPL = 0; + UINT16 segment = seg; + UINT32 offset = off; + + /* Check selector is not null */ + if((segment & ~0x07) == 0) + { + logerror("JMP: Segment is null.\n"); + FAULT(FAULT_GP,0) + } + /* Selector is within descriptor table limit */ + if((segment & 0x04) == 0) + { + /* check GDT limit */ + if((segment & ~0x07) > (cpustate->gdtr.limit)) + { + logerror("JMP: Segment is past GDT limit.\n"); + FAULT(FAULT_GP,segment & 0xfffc) + } + } + else + { + /* check LDT limit */ + if((segment & ~0x07) > (cpustate->ldtr.limit)) + { + logerror("JMP: Segment is past LDT limit.\n"); + FAULT(FAULT_GP,segment & 0xfffc) + } + } + /* Determine segment type */ + memset(&desc, 0, sizeof(desc)); + desc.selector = segment; + i386_load_protected_mode_segment(cpustate,&desc); + CPL = cpustate->CPL; // current privilege level + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = segment & 0x03; // requested privilege level + if((desc.flags & 0x0018) == 0x0018) + { + /* code segment */ + if((desc.flags & 0x0004) == 0) + { + /* non-conforming */ + SetRPL = 1; + if(RPL > CPL) + { + logerror("JMP: RPL %i is less than CPL %i\n",RPL,CPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + if(DPL != CPL) + { + logerror("JMP: DPL %i is not equal CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + } + else + { + /* conforming */ + if(DPL > CPL) + { + logerror("JMP: DPL %i is less than CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + } + if((desc.flags & 0x0080) == 0) + { + logerror("JMP: Segment is not present\n"); + FAULT(FAULT_NP,segment & 0xfffc) + } + if(offset > desc.limit) + { + logerror("JMP: Offset is past segment limit\n"); + FAULT(FAULT_GP,0) + } + } + else + { + if((desc.flags & 0x0010) != 0) + { + logerror("JMP: Segment is a data segment\n"); + FAULT(FAULT_GP,segment & 0xfffc) // #GP (cannot execute code in a data segment) + } + else + { + switch(desc.flags & 0x000f) + { + case 0x09: // 386 Available TSS + popmessage("JMP: Available TSS."); + if(DPL < CPL) + { + logerror("JMP: TSS: DPL %i is less than CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + if(DPL < RPL) + { + logerror("JMP: TSS: DPL %i is less than TSS RPL %i\n",DPL,RPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + if(call_gate.present == 0) + { + logerror("JMP: TSS: Segment is not present\n"); + FAULT(FAULT_GP,segment & 0xfffc) + } + if(call_gate.ar & 0x08) + i386_task_switch(cpustate,desc.selector,0); + else + i286_task_switch(cpustate,desc.selector,0); + break; + case 0x04: // 286 Call Gate + case 0x0c: // 386 Call Gate + popmessage("JMP: Call gate."); + SetRPL = 1; + memset(&call_gate, 0, sizeof(call_gate)); + call_gate.segment = segment; + i386_load_call_gate(cpustate,&call_gate); + DPL = call_gate.dpl; + if(DPL < CPL) + { + logerror("JMP: Call Gate: DPL %i is less than CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + if(DPL < RPL) + { + logerror("JMP: Call Gate: DPL %i is less than RPL %i\n",DPL,RPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + if((desc.flags & 0x0080) == 0) + { + logerror("JMP: Call Gate: Segment is not present\n"); + FAULT(FAULT_NP,segment & 0xfffc) + } + /* Now we examine the segment that the call gate refers to */ + if(call_gate.selector == 0) + { + logerror("JMP: Call Gate: Gate selector is null\n"); + FAULT(FAULT_GP,0) + } + if(call_gate.selector & 0x04) + { + /* check GDT limit */ + if((call_gate.selector & ~0x07) > cpustate->gdtr.limit) + { + logerror("JMP: Call Gate: Gate Selector is past GDT segment limit\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + } + else + { + /* check LDT limit */ + if((call_gate.selector & ~0x07) > cpustate->ldtr.limit) + { + logerror("JMP: Call Gate: Gate Selector is past LDT segment limit\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + } + desc.selector = call_gate.selector; + i386_load_protected_mode_segment(cpustate,&desc); + DPL = (desc.flags >> 5) & 0x03; + if((desc.flags & 0x0018) != 0x18) + { + logerror("JMP: Call Gate: Gate does not point to a code segment\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + if((desc.flags & 0x0004) == 0) + { // non-conforming + if(DPL != CPL) + { + logerror("JMP: Call Gate: Gate DPL does not equal CPL\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + } + else + { // conforming + if(DPL > CPL) + { + logerror("JMP: Call Gate: Gate DPL is greater than CPL\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + } + if((desc.flags & 0x0080) == 0) + { + logerror("JMP: Call Gate: Gate Segment is not present\n"); + FAULT(FAULT_NP,call_gate.selector & 0xfffc) + } + if(call_gate.offset > desc.limit) + { + logerror("JMP: Call Gate: Gate offset is past Gate segment limit\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + segment = call_gate.selector; + offset = call_gate.offset; + break; + case 0x05: // Task Gate + popmessage("JMP: Task gate."); + memset(&call_gate, 0, sizeof(call_gate)); + call_gate.segment = segment; + i386_load_call_gate(cpustate,&call_gate); + DPL = call_gate.dpl; + if(DPL < CPL) + { + logerror("JMP: Task Gate: Gate DPL %i is less than CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + if(DPL < RPL) + { + logerror("JMP: Task Gate: Gate DPL %i is less than CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,segment & 0xfffc) + } + if(call_gate.present == 0) + { + logerror("JMP: Task Gate: Gate is not present.\n"); + FAULT(FAULT_GP,segment & 0xfffc) + } + /* Check the TSS that the task gate points to */ + desc.selector = call_gate.selector; + i386_load_protected_mode_segment(cpustate,&desc); + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = call_gate.selector & 0x03; // requested privilege level + if(call_gate.selector & 0x04) + { + logerror("JMP: Task Gate TSS: TSS must be global.\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + else + { + if((call_gate.selector & ~0x07) > cpustate->gdtr.limit) + { + logerror("JMP: Task Gate TSS: TSS is past GDT limit.\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + } + if((call_gate.ar & 0x000f) == 0x0009) + { + logerror("JMP: Task Gate TSS: Segment is not an available TSS.\n"); + FAULT(FAULT_GP,call_gate.selector & 0xfffc) + } + if(call_gate.present == 0) + { + logerror("JMP: Task Gate TSS: TSS is not present.\n"); + FAULT(FAULT_NP,call_gate.selector & 0xfffc) + } + if(call_gate.ar & 0x08) + i386_task_switch(cpustate,call_gate.selector,0); + else + i286_task_switch(cpustate,call_gate.selector,0); + break; + default: // invalid segment type + logerror("JMP: Invalid segment type (%i) to jump to.",desc.flags & 0x000f); + FAULT(FAULT_GP,segment & 0xfffc) + } + } } + + if(SetRPL != 0) + segment = (segment & ~0x03) | cpustate->CPL; + if(operand32 == 0) + cpustate->eip = offset & 0x0000ffff; + else + cpustate->eip = offset; + cpustate->sreg[CS].selector = segment; + cpustate->performed_intersegment_jump = 1; + i386_load_segment_descriptor(cpustate,CS); + CHANGE_PC(cpustate,cpustate->eip); +} + +static void i386_protected_mode_call(i386_state *cpustate, UINT16 seg, UINT32 off, int indirect, int operand32) +{ + I386_SREG desc; + I386_CALL_GATE gate; + UINT8 SetRPL = 0; + UINT8 CPL, DPL, RPL; + UINT16 selector = seg; + UINT32 offset = off; + int x; + + if((selector & ~0x07) == 0) + { + logerror("CALL: Selector is null.\n"); + FAULT(FAULT_GP,0) // #GP(0) + } + if(selector & 0x04) + { + if((selector & ~0x07) > cpustate->ldtr.limit) + { + logerror("CALL: Selector is past LDT limit.\n"); + FAULT(FAULT_GP,selector & ~0x07) // #GP(selector) + } + } + else + { + if((selector & ~0x07) > cpustate->gdtr.limit) + { + logerror("CALL: Selector is past GDT limit.\n"); + FAULT(FAULT_GP,selector & ~0x07) // #GP(selector) + } + } + + /* Determine segment type */ + memset(&desc, 0, sizeof(desc)); + desc.selector = selector; + i386_load_protected_mode_segment(cpustate,&desc); + CPL = cpustate->CPL; // current privilege level + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = selector & 0x03; // requested privilege level + if((desc.flags & 0x0018) == 0x18) // is a code segment + { + if(desc.flags & 0x0004) + { + /* conforming */ + if(DPL > CPL) + { + logerror("CALL: Code segment DPL %i is greater than CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,selector & ~0x07) // #GP(selector) + } + } + else + { + /* non-conforming */ + if(RPL > CPL) + { + logerror("CALL: RPL %i is greater than CPL %i\n",RPL,CPL); + FAULT(FAULT_GP,selector & ~0x07) // #GP(selector) + } + if(DPL != CPL) + { + logerror("CALL: Code segment DPL %i is not equal to CPL %i\n",DPL,CPL); + FAULT(FAULT_GP,selector & ~0x07) // #GP(selector) + } + SetRPL = 1; + } + if((desc.flags & 0x0080) == 0) + { + logerror("CALL: Code segment is not present.\n"); + FAULT(FAULT_NP,selector & ~0x07) // #NP(selector) + } + if (operand32 != 0) // if 32-bit + { + if(REG32(ESP) < 8) + { + logerror("CALL: Stack has no room for return address.\n"); + FAULT(FAULT_SS,0) // #SS(0) + } + } + else + { + if(REG16(SP) < 4) + { + logerror("CALL: Stack has no room for return address.\n"); + FAULT(FAULT_SS,0) // #SS(0) + } + } + if(offset > desc.limit) + { + logerror("CALL: EIP is past segment limit.\n"); + FAULT(FAULT_GP,0) // #GP(0) + } + } + else + { + /* special segment type */ + if(desc.flags & 0x0010) + { + logerror("CALL: Segment is a data segment.\n"); + FAULT(FAULT_GP,desc.selector & ~0x07) // #GP(selector) + } + else + { + switch(desc.flags & 0x000f) + { + case 0x09: // Available 386 TSS + logerror("CALL: Available TSS (386) at %08x\n",cpustate->pc); + if(DPL < CPL) + { + logerror("CALL: TSS: DPL is less than CPL.\n"); + FAULT(FAULT_TS,selector & ~0x07) // #TS(selector) + } + if(DPL < RPL) + { + logerror("CALL: TSS: DPL is less than RPL.\n"); + FAULT(FAULT_TS,selector & ~0x07) // #TS(selector) + } + if(desc.flags & 0x0002) + { + logerror("CALL: TSS: TSS is busy.\n"); + FAULT(FAULT_TS,selector & ~0x07) // #TS(selector) + } + if(desc.flags & 0x0080) + { + logerror("CALL: TSS: Segment is not present.\n"); + FAULT(FAULT_NP,selector & ~0x07) // #NP(selector) + } + if(desc.flags & 0x08) + i386_task_switch(cpustate,desc.selector,1); + else + i286_task_switch(cpustate,desc.selector,1); + break; + case 0x04: // 286 call gate + case 0x0c: // 386 call gate + logerror("CALL: Call gate at %08x\n",cpustate->pc); + if((desc.flags & 0x000f) == 0x04) + operand32 = 0; + else + operand32 = 1; + memset(&gate, 0, sizeof(gate)); + gate.segment = selector; + i386_load_call_gate(cpustate,&gate); + DPL = gate.dpl; + if(DPL < CPL) + { + logerror("CALL: Call gate DPL %i is less than CPL %i.\n",DPL,CPL); + FAULT(FAULT_GP,desc.selector & ~0x07) // #GP(selector) + } + if(DPL < RPL) + { + logerror("CALL: Call gate DPL %i is less than RPL %i.\n",DPL,RPL); + FAULT(FAULT_GP,desc.selector & ~0x07) // #GP(selector) + } + if(gate.present == 0) + { + logerror("CALL: Call gate is not present.\n"); + FAULT(FAULT_NP,desc.selector & ~0x07) // #GP(selector) + } + desc.selector = gate.selector; + if((gate.selector & ~0x07) == 0) + { + logerror("CALL: Call gate: Segment is null.\n"); + FAULT(FAULT_GP,0) // #GP(0) + } + if(desc.selector & 0x04) + { + if((desc.selector & ~0x07) > cpustate->ldtr.limit) + { + logerror("CALL: Call gate: Segment is past LDT limit\n"); + FAULT(FAULT_GP,desc.selector & ~0x07) // #GP(selector) + } + } + else + { + if((desc.selector & ~0x07) > cpustate->gdtr.limit) + { + logerror("CALL: Call gate: Segment is past GDT limit\n"); + FAULT(FAULT_GP,desc.selector & ~0x07) // #GP(selector) + } + } + i386_load_protected_mode_segment(cpustate,&desc); + if((desc.flags & 0x0018) != 0x18) + { + logerror("CALL: Call gate: Segment is not a code segment.\n"); + FAULT(FAULT_GP,desc.selector & ~0x07) // #GP(selector) + } + DPL = ((desc.flags >> 5) & 0x03); + if(DPL > CPL) + { + logerror("CALL: Call gate: Segment DPL %i is greater than CPL %i.\n",DPL,CPL); + FAULT(FAULT_GP,desc.selector & ~0x07) // #GP(selector) + } + if(DPL < CPL && (desc.flags & 0x0004) == 0) + { + I386_SREG stack; + I386_SREG temp; + UINT32 oldSS,oldESP; + /* more privilege */ + /* Check new SS segment for privilege level from TSS */ + memset(&stack, 0, sizeof(stack)); + if(operand32 != 0) + stack.selector = i386_get_stack_segment(cpustate,DPL); + else + stack.selector = i286_get_stack_segment(cpustate,DPL); + i386_load_protected_mode_segment(cpustate,&stack); + if((stack.selector & ~0x07) == 0) + { + logerror("CALL: Call gate: TSS selector is null\n"); + FAULT(FAULT_TS,0) // #TS(0) + } + if(stack.selector & 0x04) + { + if((stack.selector & ~0x07) > cpustate->ldtr.limit) + { + logerror("CALL: Call gate: TSS selector is past LDT limit\n"); + FAULT(FAULT_TS,stack.selector) // #TS(SS selector) + } + } + else + { + if((stack.selector & ~0x07) > cpustate->gdtr.limit) + { + logerror("CALL: Call gate: TSS selector is past GDT limit\n"); + FAULT(FAULT_TS,stack.selector) // #TS(SS selector) + } + } + if((stack.selector & 0x03) != DPL) + { + logerror("CALL: Call gate: Stack selector RPL does not equal code segment DPL %i\n",DPL); + FAULT(FAULT_TS,stack.selector) // #TS(SS selector) + } + if(((stack.flags >> 5) & 0x03) != DPL) + { + logerror("CALL: Call gate: Stack DPL does not equal code segment DPL %i\n",DPL); + FAULT(FAULT_TS,stack.selector) // #TS(SS selector) + } + if((stack.flags & 0x0018) != 0x10 && (stack.flags & 0x0002)) + { + logerror("CALL: Call gate: Stack segment is not a writable data segment\n"); + FAULT(FAULT_TS,stack.selector) // #TS(SS selector) + } + if((stack.flags & 0x0080) == 0) + { + logerror("CALL: Call gate: Stack segment is not a writable data segment\n"); + FAULT(FAULT_SS,stack.selector) // #SS(SS selector) + } + if(operand32 != 0) + { + UINT32 newESP = i386_get_stack_ptr(cpustate,DPL); + if(newESP < ((gate.dword_count & 0x1f) + 16)) + { + logerror("CALL: Call gate: New stack has no room for 32-bit return address and parameters.\n"); + FAULT(FAULT_SS,0) // #SS(0) + } + if(gate.offset > desc.limit) + { + logerror("CALL: Call gate: EIP is past segment limit.\n"); + FAULT(FAULT_GP,0) // #GP(0) + } + } + else + { + UINT32 newESP = i286_get_stack_ptr(cpustate,DPL) & 0x0000ffff; + if(newESP < ((gate.dword_count & 0x1f) + 8)) + { + logerror("CALL: Call gate: New stack has no room for 16-bit return address and parameters.\n"); + FAULT(FAULT_SS,0) // #SS(0) + } + if((gate.offset & 0xffff) > desc.limit) + { + logerror("CALL: Call gate: IP is past segment limit.\n"); + FAULT(FAULT_GP,0) // #GP(0) + } + } + selector = gate.selector; + offset = gate.offset; + + /* switch to new stack */ + oldSS = cpustate->sreg[SS].selector; + if(operand32 != 0) + { + oldESP = REG32(ESP); + cpustate->sreg[SS].selector = i386_get_stack_segment(cpustate,gate.selector & 0x03); + } + else + { + oldESP = REG16(SP); + cpustate->sreg[SS].selector = i286_get_stack_segment(cpustate,gate.selector & 0x03); + } + i386_load_segment_descriptor(cpustate, SS ); + if(operand32 != 0) + REG32(ESP) = i386_get_stack_ptr(cpustate,gate.selector & 0x03); + else + REG16(SP) = i286_get_stack_ptr(cpustate,gate.selector & 0x03) & 0x0000ffff; + + if(operand32 != 0) + { + PUSH32(cpustate,oldSS); + PUSH32(cpustate,oldESP); + } + else + { + PUSH16(cpustate,oldSS); + PUSH16(cpustate,oldESP & 0xffff); + } + + memset(&temp, 0, sizeof(temp)); + temp.selector = oldSS; + i386_load_protected_mode_segment(cpustate,&temp); + /* copy parameters from old stack to new stack */ + for(x=(gate.dword_count & 0x1f)-1;x>=0;x--) + { + UINT32 addr = temp.base + oldESP + (x*2); + PUSH16(cpustate,READ16(cpustate,addr)); + } + cpustate->CPL = (stack.flags >> 5) & 0x03; + SetRPL = 1; + } + else + { + /* same privilege */ + if (operand32 != 0) // if 32-bit + { + if(REG32(ESP) < 8) + { + logerror("CALL: Stack has no room for return address.\n"); + FAULT(FAULT_SS,0) // #SS(0) + } + selector = gate.selector; + offset = gate.offset; + } + else + { + if(REG16(SP) < 4) + { + logerror("CALL: Stack has no room for return address.\n"); + FAULT(FAULT_SS,0) // #SS(0) + } + selector = gate.selector; + offset = gate.offset & 0xffff; + } + if(offset > desc.limit) + { + logerror("CALL: EIP is past segment limit.\n"); + FAULT(FAULT_GP,0) // #GP(0) + } + } + break; + case 0x05: // task gate + logerror("CALL: Task gate at %08x\n",cpustate->pc); + memset(&gate, 0, sizeof(gate)); + gate.segment = selector; + i386_load_call_gate(cpustate,&gate); + DPL = gate.dpl; + if(DPL < CPL) + { + logerror("CALL: Task Gate: Gate DPL is less than CPL.\n"); + FAULT(FAULT_TS,selector & ~0x07) // #TS(selector) + } + if(DPL < RPL) + { + logerror("CALL: Task Gate: Gate DPL is less than RPL.\n"); + FAULT(FAULT_TS,selector & ~0x07) // #TS(selector) + } + if(gate.ar & 0x0080) + { + logerror("CALL: Task Gate: Gate is not present.\n"); + FAULT(FAULT_NP,selector & ~0x07) // #NP(selector) + } + /* Check the TSS that the task gate points to */ + desc.selector = gate.selector; + i386_load_protected_mode_segment(cpustate,&desc); + if(gate.selector & 0x04) + { + logerror("CALL: Task Gate: TSS is not global.\n"); + FAULT(FAULT_TS,gate.selector & ~0x07) // #TS(selector) + } + else + { + if((gate.selector & ~0x07) > cpustate->gdtr.limit) + { + logerror("CALL: Task Gate: TSS is past GDT limit.\n"); + FAULT(FAULT_TS,gate.selector & ~0x07) // #TS(selector) + } + } + if(desc.flags & 0x0002) + { + logerror("CALL: Task Gate: TSS is busy.\n"); + FAULT(FAULT_TS,gate.selector & ~0x07) // #TS(selector) + } + if(desc.flags & 0x0080) + { + logerror("CALL: Task Gate: TSS is not present.\n"); + FAULT(FAULT_NP,gate.selector & ~0x07) // #TS(selector) + } + if(desc.flags & 0x08) + i386_task_switch(cpustate,desc.selector,1); // with nesting + else + i286_task_switch(cpustate,desc.selector,1); + break; + default: + logerror("CALL: Invalid special segment type (%i) to jump to.\n",desc.flags & 0x000f); + FAULT(FAULT_GP,selector & ~0x07) // #GP(selector) + } + } + } + + if(SetRPL != 0) + selector = (selector & ~0x03) | cpustate->CPL; + if(operand32 == 0) + { + /* 16-bit operand size */ + PUSH16(cpustate, cpustate->sreg[CS].selector ); + PUSH16(cpustate, cpustate->eip & 0x0000ffff ); + cpustate->sreg[CS].selector = selector; + cpustate->performed_intersegment_jump = 1; + cpustate->eip = offset; + i386_load_segment_descriptor(cpustate,CS); + } + else + { + /* 32-bit operand size */ + PUSH32(cpustate, cpustate->sreg[CS].selector ); + PUSH32(cpustate, cpustate->eip ); + cpustate->sreg[CS].selector = selector; + cpustate->performed_intersegment_jump = 1; + cpustate->eip = offset; + i386_load_segment_descriptor(cpustate, CS ); + } + CHANGE_PC(cpustate,cpustate->eip); +} + +static void i386_protected_mode_retf(i386_state* cpustate, UINT8 count, UINT8 operand32) +{ + UINT32 newCS, newEIP; + UINT32 newSS, newESP; // when changing privilege + I386_SREG desc; + UINT8 CPL, RPL, DPL; + + if(operand32 == 0) + { + newEIP = POP16(cpustate) & 0xffff; + newCS = POP16(cpustate) & 0xffff; + REG16(SP) += count; + } + else + { + newEIP = POP32(cpustate); + newCS = POP32(cpustate) & 0xffff; + REG32(ESP) += count; + } + + memset(&desc, 0, sizeof(desc)); + desc.selector = newCS; + i386_load_protected_mode_segment(cpustate,&desc); + CPL = cpustate->CPL; // current privilege level + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = newCS & 0x03; + + if(RPL < CPL) + { + logerror("RETF: Return segment RPL is less than CPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + + if(RPL == CPL) + { + /* same privilege level */ + if((newCS & ~0x07) == 0) + { + logerror("RETF: Return segment RPL is less than CPL.\n"); + FAULT(FAULT_GP,0) + } + if(newCS & 0x04) + { + if((newCS & ~0x07) > cpustate->ldtr.limit) + { + logerror("RETF: Return segment is past LDT limit.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + else + { + if((newCS & ~0x07) > cpustate->gdtr.limit) + { + logerror("RETF: Return segment is past GDT limit.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + if((desc.flags & 0x0018) != 0x0018) + { + logerror("RETF: Return segment is not a code segment.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + if(desc.flags & 0x0004) + { + if(DPL > CPL) + { + logerror("RETF: Conforming code segment DPL is greater than CPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + else + { + if(DPL != CPL) + { + logerror("RETF: Non-conforming code segment DPL does not equal CPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + if((desc.flags & 0x0080) == 0) + { + logerror("RETF: Code segment is not present.\n"); + FAULT(FAULT_NP,newCS & ~0x07) + } + if(newEIP > desc.limit) + { + logerror("RETF: EIP is past code segment limit.\n"); + FAULT(FAULT_GP,0) + } + if(operand32 == 0) + { + if(REG16(SP) > (cpustate->sreg[SS].limit & 0xffff)) + { + logerror("RETF: SP is past stack segment limit.\n"); + FAULT(FAULT_SS,0) + } + } + else + { + if(REG32(ESP) > cpustate->sreg[SS].limit) + { + logerror("RETF: ESP is past stack segment limit.\n"); + FAULT(FAULT_SS,0) + } + } + } + else if(RPL > CPL) + { + /* outer privilege level */ + if(operand32 == 0) + { + newESP = POP16(cpustate) & 0xffff; + newSS = POP16(cpustate); + if(newESP+8+count > cpustate->sreg[SS].limit) + { + logerror("RETF: SP is past stack segment limit.\n"); + FAULT(FAULT_SS,0) + } + } + else + { + newESP = POP32(cpustate); + newSS = POP32(cpustate); + if(newESP+16+count > cpustate->sreg[SS].limit) + { + logerror("RETF: ESP is past stack segment limit.\n"); + FAULT(FAULT_SS,0) + } + } + /* Check CS selector and descriptor */ + if((newCS & ~0x07) == 0) + { + logerror("RETF: CS segment is null.\n"); + FAULT(FAULT_GP,0) + } + if(newCS & 0x04) + { + if((newCS & ~0x07) > cpustate->ldtr.limit) + { + logerror("RETF: CS segment selector is past LDT limit.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + else + { + if((newCS & ~0x07) > cpustate->gdtr.limit) + { + logerror("RETF: CS segment selector is past GDT limit.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + if((desc.flags & 0x0018) != 0x0018) + { + logerror("RETF: CS segment is not a code segment.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + if(desc.flags & 0x0004) + { + if(DPL > RPL) + { + logerror("RETF: Conforming CS segment DPL is greater than return selector RPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + else + { + if(DPL != RPL) + { + logerror("RETF: Non-conforming CS segment DPL is not equal to return selector RPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + if((desc.flags & 0x0080) == 0) + { + logerror("RETF: CS segment is not present.\n"); + FAULT(FAULT_NP,newCS & ~0x07) + } + if(newEIP > desc.limit) + { + logerror("RETF: EIP is past return CS segment limit.\n"); + FAULT(FAULT_GP,0) + } + /* Check SS selector and descriptor */ + desc.selector = newSS; + i386_load_protected_mode_segment(cpustate,&desc); + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + if((newSS & ~0x07) == 0) + { + logerror("RETF: SS segment is null.\n"); + FAULT(FAULT_GP,0) + } + if(newSS & 0x04) + { + if((newSS & ~0x07) > cpustate->ldtr.limit) + { + logerror("RETF: SS segment selector is past LDT limit.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + } + else + { + if((newSS & ~0x07) > cpustate->gdtr.limit) + { + logerror("RETF: SS segment selector is past GDT limit.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + } + if((newSS & 0x03) != RPL) + { + logerror("RETF: SS segment RPL is not equal to CS segment RPL.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + if((desc.flags & 0x0018) != 0x0010 || (desc.flags & 0x0002) == 0) + { + logerror("RETF: SS segment is not a writable data segment.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + if(((desc.flags >> 5) & 0x03) != RPL) + { + logerror("RETF: SS DPL is not equal to CS segment RPL.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + if((desc.flags & 0x0080) == 0) + { + logerror("RETF: SS segment is not present.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + cpustate->CPL = newCS & 0x03; + + /* Load new SS:(E)SP */ + if(operand32 == 0) + REG16(SP) = newESP & 0xffff; + else + REG32(ESP) = newESP; + cpustate->sreg[SS].selector = newSS; + i386_load_segment_descriptor(cpustate, SS ); + + /* Check that DS, ES, FS and GS are valid for the new privilege level */ + i386_check_sreg_validity(cpustate,DS); + i386_check_sreg_validity(cpustate,ES); + i386_check_sreg_validity(cpustate,FS); + i386_check_sreg_validity(cpustate,GS); + } + + /* Load new CS:(E)IP */ + if(operand32 == 0) + cpustate->eip = newEIP & 0xffff; + else + cpustate->eip = newEIP; + cpustate->sreg[CS].selector = newCS; + i386_load_segment_descriptor(cpustate, CS ); + CHANGE_PC(cpustate,cpustate->eip); +} + +static void i386_protected_mode_iret(i386_state* cpustate, int operand32) +{ + UINT32 newCS, newEIP; + UINT32 newSS, newESP; // when changing privilege + I386_SREG desc; + UINT8 CPL, RPL, DPL; + UINT32 newflags; + + CPL = cpustate->CPL; + if(operand32 == 0) + { + newEIP = POP16(cpustate); + newCS = POP16(cpustate); + newflags = POP16(cpustate); + } + else + { + newEIP = POP32(cpustate); + newCS = POP32(cpustate) & 0xffff; + newflags = POP32(cpustate); + } + + if(V8086_MODE) + { + logerror("IRET: Is in Virtual 8086 mode.\n"); + FAULT(FAULT_GP,0) + } + else if(NESTED_TASK) + { + UINT32 task = READ32(cpustate,cpustate->task.base); + /* Task Return */ + popmessage("IRET: Nested task return."); + /* Check back-link selector in TSS */ + if(task & 0x04) + { + logerror("IRET: Task return: Back-linked TSS is not in GDT.\n"); + FAULT(FAULT_TS,task & ~0x07) + } + if((task & ~0x07) > cpustate->gdtr.limit) + { + logerror("IRET: Task return: Back-linked TSS is not in GDT.\n"); + FAULT(FAULT_TS,task & ~0x07) + } + memset(&desc, 0, sizeof(desc)); + desc.selector = task; + i386_load_protected_mode_segment(cpustate,&desc); + if((desc.flags & 0x001f) != 0x000b) + { + logerror("IRET: Task return: Back-linked TSS is not a busy TSS.\n"); + FAULT(FAULT_TS,task & ~0x07) + } + if((desc.flags & 0x0080) == 0) + { + logerror("IRET: Task return: Back-linked TSS is not present.\n"); + FAULT(FAULT_NP,task & ~0x07) + } + i386_task_switch(cpustate,task,0); // no nesting + if(desc.flags & 0x08) + i386_task_switch(cpustate,desc.selector,0); + else + i286_task_switch(cpustate,desc.selector,0); + } + else + { + if(newflags & 0x00020000) // if returning to virtual 8086 mode + { + /* Return to v86 mode */ + popmessage("IRET: Unimplemented return to Virtual 8086 mode."); + } + else + { + if(operand32 == 0) + { + if(REG16(SP)+4 > cpustate->sreg[SS].limit) + { + logerror("IRET: Data on stack is past SS limit.\n"); + FAULT(FAULT_SS,0) + } + } + else + { + if(REG32(ESP)+6 > cpustate->sreg[SS].limit) + { + logerror("IRET: Data on stack is past SS limit.\n"); + FAULT(FAULT_SS,0) + } + } + RPL = newCS & 0x03; + if(RPL < CPL) + { + logerror("IRET: Return CS RPL is less than CPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + if(RPL == CPL) + { + /* return to same privilege level */ + if(operand32 == 0) + { + if(REG16(SP)+6 > cpustate->sreg[SS].limit) + { + logerror("IRET: Data on stack is past SS limit.\n"); + FAULT(FAULT_SS,0) + } + } + else + { + if(REG32(ESP)+12 > cpustate->sreg[SS].limit) + { + logerror("IRET: Data on stack is past SS limit.\n"); + FAULT(FAULT_SS,0) + } + } + if((newCS & ~0x07) == 0) + { + logerror("IRET: Return CS selector is null.\n"); + FAULT(FAULT_GP,0) + } + if(newCS & 0x04) + { + if((newCS & ~0x07) > cpustate->ldtr.limit) + { + logerror("IRET: Return CS selector (%04x) is past LDT limit.\n",newCS); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + else + { + if((newCS & ~0x07) > cpustate->gdtr.limit) + { + logerror("IRET: Return CS selector is past GDT limit.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + memset(&desc, 0, sizeof(desc)); + desc.selector = newCS; + i386_load_protected_mode_segment(cpustate,&desc); + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = newCS & 0x03; + if((desc.flags & 0x0018) != 0x0018) + { + logerror("IRET: Return CS segment is not a code segment.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + if(desc.flags & 0x0004) + { + if(DPL > CPL) + { + logerror("IRET: Conforming return CS DPL is greater than CPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + else + { + if(DPL != CPL) + { + logerror("IRET: Non-conforming return CS DPL is not equal to CPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + if((desc.flags & 0x0080) == 0) + { + logerror("IRET: Return CS segment is not present.\n"); + FAULT(FAULT_NP,newCS & ~0x07) + } + if(newEIP > desc.limit) + { + logerror("IRET: Return EIP is past return CS limit.\n"); + FAULT(FAULT_GP,0) } + + if(operand32 == 0) + { + cpustate->eip = newEIP; + cpustate->sreg[CS].selector = newCS; + set_flags(cpustate,newflags); + } + else + { + cpustate->eip = newEIP; + cpustate->sreg[CS].selector = newCS & 0xffff; + set_flags(cpustate,newflags); + } + } + else if(RPL > CPL) + { + I386_SREG stack; + /* return to outer privilege level */ + if(operand32 == 0) + { + newESP = POP16(cpustate); + newSS = POP16(cpustate); + if(REG16(SP) > cpustate->sreg[SS].limit) + { + logerror("IRET: ESP is past SS limit.\n"); + FAULT(FAULT_SS,0) } + } + else + { + newESP = POP32(cpustate); + newSS = POP32(cpustate) & 0xffff; + if(REG32(ESP) > cpustate->sreg[SS].limit) + { + logerror("IRET: ESP is past SS limit.\n"); + FAULT(FAULT_SS,0) + } + } + memset(&desc, 0, sizeof(desc)); + desc.selector = newCS; + i386_load_protected_mode_segment(cpustate,&desc); + DPL = (desc.flags >> 5) & 0x03; // descriptor privilege level + RPL = newCS & 0x03; + memset(&stack, 0, sizeof(stack)); + stack.selector = newSS; + i386_load_protected_mode_segment(cpustate,&stack); + /* Check CS selector and descriptor */ + if((newCS & ~0x07) == 0) + { + logerror("IRET: Return CS selector is null.\n"); + FAULT(FAULT_GP,0) + } + if(newCS & 0x04) + { + if((newCS & ~0x07) > cpustate->ldtr.limit) + { + logerror("IRET: Return CS selector is past LDT limit.\n"); + FAULT(FAULT_GP,newCS & ~0x07); + } + } + else + { + if((newCS & ~0x07) > cpustate->gdtr.limit) + { + logerror("IRET: Return CS selector is past GDT limit.\n"); + FAULT(FAULT_GP,newCS & ~0x07); + } + } + if((desc.flags & 0x0018) != 0x0018) + { + logerror("IRET: Return CS segment is not a code segment.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + if(desc.flags & 0x0004) + { + if(DPL <= CPL) + { + logerror("IRET: Conforming return CS DPL is not greater than CPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + else + { + if(DPL != RPL) + { + logerror("IRET: Non-conforming return CS DPL does not equal CS RPL.\n"); + FAULT(FAULT_GP,newCS & ~0x07) + } + } + if((desc.flags & 0x0080) == 0) + { + logerror("IRET: Return CS segment is not present.\n"); + FAULT(FAULT_NP,newCS & ~0x07) + } + + /* Check SS selector and descriptor */ + DPL = (stack.flags >> 5) & 0x03; + if((newSS & ~0x07) == 0) + { + logerror("IRET: Return SS selector is null.\n"); + FAULT(FAULT_GP,0) + } + if(newSS & 0x04) + { + if((newSS & ~0x07) > cpustate->ldtr.limit) + { + logerror("IRET: Return SS selector is past LDT limit.\n"); + FAULT(FAULT_GP,newSS & ~0x07); + } + } + else + { + if((newSS & ~0x07) > cpustate->gdtr.limit) + { + logerror("IRET: Return SS selector is past GDT limit.\n"); + FAULT(FAULT_GP,newSS & ~0x07); + } + } + if((newSS & 0x03) != RPL) + { + logerror("IRET: Return SS RPL is not equal to return CS RPL.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + if((stack.flags & 0x0018) != 0x0010) + { + logerror("IRET: Return SS segment is not a data segment.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + if((stack.flags & 0x0002) == 0) + { + logerror("IRET: Return SS segment is not writable.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + if(DPL != RPL) + { + logerror("IRET: Return SS DPL does not equal SS RPL.\n"); + FAULT(FAULT_GP,newSS & ~0x07) + } + if((stack.flags & 0x0080) == 0) + { + logerror("IRET: Return SS segment is not present.\n"); + FAULT(FAULT_NP,newSS & ~0x07) + } + if(newEIP > desc.limit) + { + logerror("IRET: EIP is past return CS limit.\n"); + FAULT(FAULT_GP,0) + } + + if(operand32 == 0) + { + cpustate->eip = newEIP & 0xffff; + cpustate->sreg[CS].selector = newCS; + set_flags(cpustate,newflags); + REG16(SP) = newESP & 0xffff; + cpustate->sreg[SS].selector = newSS; + } + else + { + cpustate->eip = newEIP; + cpustate->sreg[CS].selector = newCS & 0xffff; + set_flags(cpustate,newflags); + REG32(ESP) = newESP; + cpustate->sreg[SS].selector = newSS & 0xffff; + } + cpustate->CPL = newCS & 0x03; + i386_load_segment_descriptor(cpustate,SS); + + /* Check that DS, ES, FS and GS are valid for the new privilege level */ + i386_check_sreg_validity(cpustate,DS); + i386_check_sreg_validity(cpustate,ES); + i386_check_sreg_validity(cpustate,FS); + i386_check_sreg_validity(cpustate,GS); + } + } + } + + i386_load_segment_descriptor(cpustate,CS); + CHANGE_PC(cpustate,cpustate->eip); } #include "cycles.h" @@ -684,6 +2562,8 @@ static CPU_RESET( i386 ) REG32(EAX) = 0; REG32(EDX) = (3 << 8) | (0 << 4) | (8); + cpustate->CPL = 0; + build_opcode_table(cpustate, OP_I386); cpustate->cycle_table_rm = cycle_table_rm[CPU_CYCLES_I386]; cpustate->cycle_table_pm = cycle_table_pm[CPU_CYCLES_I386]; @@ -702,7 +2582,7 @@ static void i386_set_irq_line(i386_state *cpustate,int irqline, int state) { /* NMI (I do not think that this is 100% right) */ if ( state ) - i386_trap(cpustate,2, 1); + i386_trap(cpustate,2, 1, 0); } else { @@ -1090,6 +2970,7 @@ CPU_GET_INFO( i386 ) case CPUINFO_STR_REGISTER + I386_TR_BASE: sprintf(info->s, "TRBASE: %08X", cpustate->task.base); break; case CPUINFO_STR_REGISTER + I386_TR_LIMIT: sprintf(info->s, "TRLIMIT: %08X", cpustate->task.limit); break; case CPUINFO_STR_REGISTER + I386_TR_FLAGS: sprintf(info->s, "TRFLAGS: %04X", cpustate->task.flags); break; + case CPUINFO_STR_REGISTER + I386_CPL: sprintf(info->s, "CPL: %01X", cpustate->CPL); break; } } diff --git a/src/emu/cpu/i386/i386op16.c b/src/emu/cpu/i386/i386op16.c index 72be548507f..59edeb5f1f7 100644 --- a/src/emu/cpu/i386/i386op16.c +++ b/src/emu/cpu/i386/i386op16.c @@ -447,21 +447,16 @@ static void I386OP(call_abs16)(i386_state *cpustate) // Opcode 0x9a UINT16 offset = FETCH16(cpustate); UINT16 ptr = FETCH16(cpustate); - if( PROTECTED_MODE ) { - /* TODO */ - fatalerror("i386: call_abs16 in protected mode unimplemented"); - } else { - if (cpustate->sreg[CS].d) - { - PUSH32(cpustate, cpustate->sreg[CS].selector ); - PUSH32(cpustate, cpustate->eip ); - } - else - { - PUSH16(cpustate, cpustate->sreg[CS].selector ); - PUSH16(cpustate, cpustate->eip ); - } + if( PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_call(cpustate,ptr,offset,0,0); + } + else + { + PUSH16(cpustate, cpustate->sreg[CS].selector ); + PUSH16(cpustate, cpustate->eip ); cpustate->sreg[CS].selector = ptr; + cpustate->performed_intersegment_jump = 1; cpustate->eip = offset; i386_load_segment_descriptor(cpustate,CS); } @@ -745,17 +740,12 @@ static void I386OP(inc_di)(i386_state *cpustate) // Opcode 0x47 static void I386OP(iret16)(i386_state *cpustate) // Opcode 0xcf { - if( PROTECTED_MODE ) { - /* TODO: Virtual 8086-mode */ - /* TODO: Nested task */ - /* TODO: #SS(0) exception */ - /* TODO: All the protection-related stuff... */ - cpustate->eip = POP16(cpustate); - cpustate->sreg[CS].selector = POP16(cpustate); - set_flags(cpustate, POP16(cpustate) ); - i386_load_segment_descriptor(cpustate,CS); - CHANGE_PC(cpustate,cpustate->eip); - } else { + if( PROTECTED_MODE ) + { + i386_protected_mode_iret(cpustate,0); + } + else + { /* TODO: #SS(0) exception */ /* TODO: #GP(0) exception */ cpustate->eip = POP16(cpustate); @@ -1111,15 +1101,12 @@ static void I386OP(jmp_abs16)(i386_state *cpustate) // Opcode 0xea UINT16 address = FETCH16(cpustate); UINT16 segment = FETCH16(cpustate); - if( PROTECTED_MODE ) { - /* TODO: #GP */ - /* TODO: access rights, etc. */ - cpustate->eip = address; - cpustate->sreg[CS].selector = segment; - cpustate->performed_intersegment_jump = 1; - i386_load_segment_descriptor(cpustate,CS); - CHANGE_PC(cpustate,cpustate->eip); - } else { + if( PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_jump(cpustate,segment,address,0,0); + } + else + { cpustate->eip = address; cpustate->sreg[CS].selector = segment; cpustate->performed_intersegment_jump = 1; @@ -2737,21 +2724,32 @@ static void I386OP(groupFF_16)(i386_state *cpustate) // Opcode 0xff case 3: /* CALL FAR Rm16 */ { UINT16 address, selector; - if( modrm >= 0xc0 ) { + if( modrm >= 0xc0 ) + { fatalerror("i386: groupFF_16 /%d NYI", (modrm >> 3) & 0x7); - } else { + } + else + { UINT32 ea = GetEA(cpustate,modrm); address = READ16(cpustate,ea + 0); selector = READ16(cpustate,ea + 2); CYCLES(cpustate,CYCLES_CALL_MEM_INTERSEG); /* TODO: Timing = 10 + m */ + + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_call(cpustate,selector,address,1,0); + } + else + { + PUSH16(cpustate, cpustate->sreg[CS].selector ); + PUSH16(cpustate, cpustate->eip ); + cpustate->sreg[CS].selector = selector; + cpustate->performed_intersegment_jump = 1; + i386_load_segment_descriptor(cpustate, CS ); + cpustate->eip = address; + CHANGE_PC(cpustate,cpustate->eip); + } } - PUSH16(cpustate, cpustate->sreg[CS].selector ); - PUSH16(cpustate, cpustate->eip ); - cpustate->sreg[CS].selector = selector; - cpustate->performed_intersegment_jump = 1; - i386_load_segment_descriptor(cpustate, CS ); - cpustate->eip = address; - CHANGE_PC(cpustate,cpustate->eip); } break; case 4: /* JMP Rm16 */ @@ -2772,19 +2770,30 @@ static void I386OP(groupFF_16)(i386_state *cpustate) // Opcode 0xff case 5: /* JMP FAR Rm16 */ { UINT16 address, selector; - if( modrm >= 0xc0 ) { + + if( modrm >= 0xc0 ) + { fatalerror("i386: groupFF_16 /%d NYI", (modrm >> 3) & 0x7); - } else { + } + else + { UINT32 ea = GetEA(cpustate,modrm); address = READ16(cpustate,ea + 0); selector = READ16(cpustate,ea + 2); CYCLES(cpustate,CYCLES_JMP_MEM_INTERSEG); /* TODO: Timing = 10 + m */ + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_jump(cpustate,selector,address,1,0); + } + else + { + cpustate->sreg[CS].selector = selector; + cpustate->performed_intersegment_jump = 1; + i386_load_segment_descriptor(cpustate, CS ); + cpustate->eip = address; + CHANGE_PC(cpustate,cpustate->eip); + } } - cpustate->sreg[CS].selector = selector; - cpustate->performed_intersegment_jump = 1; - i386_load_segment_descriptor(cpustate, CS ); - cpustate->eip = address; - CHANGE_PC(cpustate,cpustate->eip); } break; case 6: /* PUSH Rm16 */ @@ -2833,7 +2842,7 @@ static void I386OP(group0F00_16)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; case 1: /* STR */ @@ -2851,7 +2860,7 @@ static void I386OP(group0F00_16)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; case 2: /* LLDT */ @@ -2875,7 +2884,7 @@ static void I386OP(group0F00_16)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; @@ -2900,7 +2909,7 @@ static void I386OP(group0F00_16)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; @@ -2962,7 +2971,7 @@ static void I386OP(group0F00_16)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); logerror("i386: VERR: Exception - Running in real mode or virtual 8086 mode.\n"); } break; @@ -3020,7 +3029,7 @@ static void I386OP(group0F00_16)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); logerror("i386: VERW: Exception - Running in real mode or virtual 8086 mode.\n"); } break; @@ -3304,7 +3313,7 @@ static void I386OP(lar_r16_rm16)(i386_state *cpustate) // Opcode 0x0f 0x02 else { // illegal opcode - i386_trap(cpustate,6,0); + i386_trap(cpustate,6,0, 0); logerror("i386: LAR: Exception - running in real mode or virtual 8086 mode.\n"); } } @@ -3333,7 +3342,7 @@ static void I386OP(lsl_r16_rm16)(i386_state *cpustate) // Opcode 0x0f 0x03 } } else - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } static void I386OP(bound_r16_m16_m16)(i386_state *cpustate) // Opcode 0x62 @@ -3358,7 +3367,7 @@ static void I386OP(bound_r16_m16_m16)(i386_state *cpustate) // Opcode 0x62 if ((val < low) || (val > high)) { CYCLES(cpustate,CYCLES_BOUND_OUT_RANGE); - i386_trap(cpustate,5, 0); + i386_trap(cpustate,5, 0, 0); } else { @@ -3368,10 +3377,17 @@ static void I386OP(bound_r16_m16_m16)(i386_state *cpustate) // Opcode 0x62 static void I386OP(retf16)(i386_state *cpustate) // Opcode 0xcb { - cpustate->eip = POP16(cpustate); - cpustate->sreg[CS].selector = POP16(cpustate); - i386_load_segment_descriptor(cpustate, CS ); - CHANGE_PC(cpustate,cpustate->eip); + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_retf(cpustate,0,0); + } + else + { + cpustate->eip = POP16(cpustate); + cpustate->sreg[CS].selector = POP16(cpustate); + i386_load_segment_descriptor(cpustate, CS ); + CHANGE_PC(cpustate,cpustate->eip); + } CYCLES(cpustate,CYCLES_RET_INTERSEG); } @@ -3380,12 +3396,19 @@ static void I386OP(retf_i16)(i386_state *cpustate) // Opcode 0xca { UINT16 count = FETCH16(cpustate); - cpustate->eip = POP16(cpustate); - cpustate->sreg[CS].selector = POP16(cpustate); - i386_load_segment_descriptor(cpustate, CS ); - CHANGE_PC(cpustate,cpustate->eip); + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_retf(cpustate,count,0); + } + else + { + cpustate->eip = POP16(cpustate); + cpustate->sreg[CS].selector = POP16(cpustate); + i386_load_segment_descriptor(cpustate, CS ); + CHANGE_PC(cpustate,cpustate->eip); + REG16(SP) += count; + } - REG16(SP) += count; CYCLES(cpustate,CYCLES_RET_IMM_INTERSEG); } diff --git a/src/emu/cpu/i386/i386op32.c b/src/emu/cpu/i386/i386op32.c index 47e4bc530f1..6b0d3868c4e 100644 --- a/src/emu/cpu/i386/i386op32.c +++ b/src/emu/cpu/i386/i386op32.c @@ -448,13 +448,16 @@ static void I386OP(call_abs32)(i386_state *cpustate) // Opcode 0x9a UINT32 offset = FETCH32(cpustate); UINT16 ptr = FETCH16(cpustate); - if( PROTECTED_MODE ) { - /* TODO */ - fatalerror("i386: call_abs32 in protected mode unimplemented"); - } else { + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_call(cpustate,ptr,offset,0,1); + } + else + { PUSH32(cpustate, cpustate->sreg[CS].selector ); PUSH32(cpustate, cpustate->eip ); cpustate->sreg[CS].selector = ptr; + cpustate->performed_intersegment_jump = 1; cpustate->eip = offset; i386_load_segment_descriptor(cpustate,CS); } @@ -730,17 +733,12 @@ static void I386OP(inc_edi)(i386_state *cpustate) // Opcode 0x47 static void I386OP(iret32)(i386_state *cpustate) // Opcode 0xcf { - if( PROTECTED_MODE ) { - /* TODO: Virtual 8086-mode */ - /* TODO: Nested task */ - /* TODO: #SS(0) exception */ - /* TODO: All the protection-related stuff... */ - cpustate->eip = POP32(cpustate); - cpustate->sreg[CS].selector = POP32(cpustate) & 0xffff; - set_flags(cpustate, POP32(cpustate) ); - i386_load_segment_descriptor(cpustate,CS); - CHANGE_PC(cpustate,cpustate->eip); - } else { + if( PROTECTED_MODE ) + { + i386_protected_mode_iret(cpustate,1); + } + else + { /* TODO: #SS(0) exception */ /* TODO: #GP(0) exception */ cpustate->eip = POP32(cpustate); @@ -970,16 +968,15 @@ static void I386OP(jmp_abs32)(i386_state *cpustate) // Opcode 0xea UINT32 address = FETCH32(cpustate); UINT16 segment = FETCH16(cpustate); - if( PROTECTED_MODE ) { - /* TODO: #GP */ - /* TODO: access rights, etc. */ - cpustate->eip = address; - cpustate->sreg[CS].selector = segment; - i386_load_segment_descriptor(cpustate,CS); - CHANGE_PC(cpustate,cpustate->eip); - } else { + if( PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_jump(cpustate,segment,address,0,1); + } + else + { cpustate->eip = address; cpustate->sreg[CS].selector = segment; + cpustate->performed_intersegment_jump = 1; i386_load_segment_descriptor(cpustate,CS); CHANGE_PC(cpustate,cpustate->eip); } @@ -2578,21 +2575,32 @@ static void I386OP(groupFF_32)(i386_state *cpustate) // Opcode 0xff { UINT16 selector; UINT32 address; - if( modrm >= 0xc0 ) { + + if( modrm >= 0xc0 ) + { fatalerror("i386: groupFF_32 /%d: NYI", (modrm >> 3) & 0x7); - } else { + } + else + { UINT32 ea = GetEA(cpustate,modrm); address = READ32(cpustate,ea + 0); selector = READ16(cpustate,ea + 4); CYCLES(cpustate,CYCLES_CALL_MEM_INTERSEG); /* TODO: Timing = 10 + m */ + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_call(cpustate,selector,address,1,1); + } + else + { + PUSH32(cpustate, cpustate->sreg[CS].selector ); + PUSH32(cpustate, cpustate->eip ); + cpustate->sreg[CS].selector = selector; + cpustate->performed_intersegment_jump = 1; + i386_load_segment_descriptor(cpustate, CS ); + cpustate->eip = address; + CHANGE_PC(cpustate,cpustate->eip); + } } - PUSH32(cpustate, cpustate->sreg[CS].selector ); - PUSH32(cpustate, cpustate->eip ); - cpustate->sreg[CS].selector = selector; - cpustate->performed_intersegment_jump = 1; - i386_load_segment_descriptor(cpustate, CS ); - cpustate->eip = address; - CHANGE_PC(cpustate,cpustate->eip); } break; case 4: /* JMP Rm32 */ @@ -2614,19 +2622,30 @@ static void I386OP(groupFF_32)(i386_state *cpustate) // Opcode 0xff { UINT16 selector; UINT32 address; - if( modrm >= 0xc0 ) { + + if( modrm >= 0xc0 ) + { fatalerror("i386: groupFF_32 /%d: NYI", (modrm >> 3) & 0x7); - } else { + } + else + { UINT32 ea = GetEA(cpustate,modrm); address = READ32(cpustate,ea + 0); selector = READ16(cpustate,ea + 4); CYCLES(cpustate,CYCLES_JMP_MEM_INTERSEG); /* TODO: Timing = 10 + m */ + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_jump(cpustate,selector,address,1,1); + } + else + { + cpustate->sreg[CS].selector = selector; + cpustate->performed_intersegment_jump = 1; + i386_load_segment_descriptor(cpustate, CS ); + cpustate->eip = address; + CHANGE_PC(cpustate,cpustate->eip); + } } - cpustate->sreg[CS].selector = selector; - cpustate->performed_intersegment_jump = 1; - i386_load_segment_descriptor(cpustate, CS ); - cpustate->eip = address; - CHANGE_PC(cpustate,cpustate->eip); } break; case 6: /* PUSH Rm32 */ @@ -2672,7 +2691,7 @@ static void I386OP(group0F00_32)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; case 1: /* STR */ @@ -2690,7 +2709,7 @@ static void I386OP(group0F00_32)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; case 2: /* LLDT */ @@ -2714,7 +2733,7 @@ static void I386OP(group0F00_32)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; @@ -2739,7 +2758,7 @@ static void I386OP(group0F00_32)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } break; @@ -2801,7 +2820,7 @@ static void I386OP(group0F00_32)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); logerror("i386: VERR: Exception - Running in real mode or virtual 8086 mode.\n"); } break; @@ -2859,7 +2878,7 @@ static void I386OP(group0F00_32)(i386_state *cpustate) // Opcode 0x0f 00 } else { - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); logerror("i386: VERW: Exception - Running in real mode or virtual 8086 mode.\n"); } break; @@ -3111,7 +3130,7 @@ static void I386OP(lar_r32_rm32)(i386_state *cpustate) // Opcode 0x0f 0x02 else { // illegal opcode - i386_trap(cpustate,6,0); + i386_trap(cpustate,6,0, 0); logerror("i386: LAR: Exception - running in real mode or virtual 8086 mode.\n"); } } @@ -3140,7 +3159,7 @@ static void I386OP(lsl_r32_rm32)(i386_state *cpustate) // Opcode 0x0f 0x03 } } else - i386_trap(cpustate,6, 0); + i386_trap(cpustate,6, 0, 0); } static void I386OP(bound_r32_m32_m32)(i386_state *cpustate) // Opcode 0x62 @@ -3165,7 +3184,7 @@ static void I386OP(bound_r32_m32_m32)(i386_state *cpustate) // Opcode 0x62 if ((val < low) || (val > high)) { CYCLES(cpustate,CYCLES_BOUND_OUT_RANGE); - i386_trap(cpustate,5, 0); + i386_trap(cpustate,5, 0, 0); } else { @@ -3175,10 +3194,17 @@ static void I386OP(bound_r32_m32_m32)(i386_state *cpustate) // Opcode 0x62 static void I386OP(retf32)(i386_state *cpustate) // Opcode 0xcb { - cpustate->eip = POP32(cpustate); - cpustate->sreg[CS].selector = POP32(cpustate); - i386_load_segment_descriptor(cpustate, CS ); - CHANGE_PC(cpustate,cpustate->eip); + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_retf(cpustate,0,1); + } + else + { + cpustate->eip = POP32(cpustate); + cpustate->sreg[CS].selector = POP32(cpustate); + i386_load_segment_descriptor(cpustate, CS ); + CHANGE_PC(cpustate,cpustate->eip); + } CYCLES(cpustate,CYCLES_RET_INTERSEG); } @@ -3187,12 +3213,19 @@ static void I386OP(retf_i32)(i386_state *cpustate) // Opcode 0xca { UINT16 count = FETCH16(cpustate); - cpustate->eip = POP32(cpustate); - cpustate->sreg[CS].selector = POP32(cpustate); - i386_load_segment_descriptor(cpustate, CS ); - CHANGE_PC(cpustate,cpustate->eip); + if(PROTECTED_MODE && !V8086_MODE) + { + i386_protected_mode_retf(cpustate,count,1); + } + else + { + cpustate->eip = POP32(cpustate); + cpustate->sreg[CS].selector = POP32(cpustate); + i386_load_segment_descriptor(cpustate, CS ); + CHANGE_PC(cpustate,cpustate->eip); + REG32(ESP) += count; + } - REG32(ESP) += count; CYCLES(cpustate,CYCLES_RET_IMM_INTERSEG); } diff --git a/src/emu/cpu/i386/i386ops.c b/src/emu/cpu/i386/i386ops.c index 85f8eb4ad08..c6b8778a728 100644 --- a/src/emu/cpu/i386/i386ops.c +++ b/src/emu/cpu/i386/i386ops.c @@ -2197,20 +2197,20 @@ static void I386OP(nop)(i386_state *cpustate) // Opcode 0x90 static void I386OP(int3)(i386_state *cpustate) // Opcode 0xcc { CYCLES(cpustate,CYCLES_INT3); - i386_trap(cpustate,3, 1); + i386_trap(cpustate,3, 1, 0); } static void I386OP(int)(i386_state *cpustate) // Opcode 0xcd { int interrupt = FETCH(cpustate); CYCLES(cpustate,CYCLES_INT); - i386_trap(cpustate,interrupt, 1); + i386_trap(cpustate,interrupt, 1, 0); } static void I386OP(into)(i386_state *cpustate) // Opcode 0xce { if( cpustate->OF ) { - i386_trap(cpustate,4, 1); + i386_trap(cpustate,4, 1, 0); CYCLES(cpustate,CYCLES_INTO_OF1); } else @@ -2372,5 +2372,5 @@ static void I386OP(unimplemented)(i386_state *cpustate) static void I386OP(invalid)(i386_state *cpustate) { logerror("i386: Invalid opcode %02X at %08X\n", cpustate->opcode, cpustate->pc - 1); - i386_trap(cpustate, 6, 0); + i386_trap(cpustate, 6, 0, 0); } diff --git a/src/emu/cpu/i386/i386priv.h b/src/emu/cpu/i386/i386priv.h index 485e879fa5f..931c0b5eb65 100644 --- a/src/emu/cpu/i386/i386priv.h +++ b/src/emu/cpu/i386/i386priv.h @@ -136,6 +136,8 @@ enum I386_LDTR_LIMIT, I386_LDTR_FLAGS, + I386_CPL, + X87_CTRL, X87_STATUS, X87_ST0, @@ -148,6 +150,17 @@ enum X87_ST7, }; +/* Protected mode exceptions */ +#define FAULT_UD 6 // Invalid Opcode +#define FAULT_NM 7 // Coprocessor not available +#define FAULT_DF 8 // Double Fault +#define FAULT_TS 10 // Invalid TSS +#define FAULT_NP 11 // Segment or Gate not present +#define FAULT_SS 12 // Stack fault +#define FAULT_GP 13 // General Protection Fault +#define FAULT_PF 14 // Page Fault +#define FAULT_MF 16 // Match (Coprocessor) Fault + typedef struct { UINT16 selector; UINT16 flags; @@ -156,6 +169,17 @@ typedef struct { int d; // Operand size } I386_SREG; +typedef struct +{ + UINT16 segment; + UINT16 selector; + UINT32 offset; + UINT8 ar; // access rights + UINT8 dpl; + UINT8 dword_count; + UINT8 present; +} I386_CALL_GATE; + typedef struct { UINT32 base; UINT16 limit; @@ -202,6 +226,8 @@ struct _i386_state UINT8 IOP2; UINT8 NT; + UINT8 CPL; // current privilege level + UINT8 performed_intersegment_jump; UINT32 cr[5]; // Control registers @@ -273,6 +299,7 @@ extern int i386_parity_table[256]; #define PROTECTED_MODE (cpustate->cr[0] & 0x1) #define STACK_32BIT (cpustate->sreg[SS].d) #define V8086_MODE (cpustate->eflags & 0x00020000) +#define NESTED_TASK (cpustate->eflags & 0x00004000) #define SetOF_Add32(r,s,d) (cpustate->OF = (((r) ^ (s)) & ((r) ^ (d)) & 0x80000000) ? 1: 0) #define SetOF_Add16(r,s,d) (cpustate->OF = (((r) ^ (s)) & ((r) ^ (d)) & 0x8000) ? 1 : 0) diff --git a/src/emu/cpu/i386/i486ops.c b/src/emu/cpu/i386/i486ops.c index 5cf18d897a3..1c5ff7388da 100644 --- a/src/emu/cpu/i386/i486ops.c +++ b/src/emu/cpu/i386/i486ops.c @@ -6,7 +6,7 @@ static void I486OP(cpuid)(i386_state *cpustate) // Opcode 0x0F A2 { // this 486 doesn't support the CPUID instruction logerror("CPUID not supported at %08x!\n", cpustate->eip); - i386_trap(cpustate, 6, 0); + i386_trap(cpustate, 6, 0, 0); } else { diff --git a/src/emu/cpu/i386/x87ops.c b/src/emu/cpu/i386/x87ops.c index 8425dd92d56..138d0c5cf08 100644 --- a/src/emu/cpu/i386/x87ops.c +++ b/src/emu/cpu/i386/x87ops.c @@ -79,6 +79,19 @@ static void I386OP(fpu_group_d9)(i386_state *cpustate) // Opcode 0xd9 break; } + case 6: // FSTENV + { // TODO: 32-bit operand size + WRITE16(cpustate,ea, cpustate->fpu_control_word); + WRITE16(cpustate,ea+2, cpustate->fpu_status_word); + WRITE16(cpustate,ea+4, cpustate->fpu_tag_word); + WRITE16(cpustate,ea+6, cpustate->fpu_inst_ptr & 0xffff); + WRITE16(cpustate,ea+8, (cpustate->fpu_opcode & 0x07ff) | ((cpustate->fpu_inst_ptr & 0x0f0000) >> 4)); + WRITE16(cpustate,ea+10, cpustate->fpu_data_ptr & 0xffff); + WRITE16(cpustate,ea+12, ((cpustate->fpu_inst_ptr & 0x0f0000) >> 4)); + CYCLES(cpustate,1); // TODO + break; + } + case 7: // FSTCW { WRITE16(cpustate,ea, cpustate->fpu_control_word); |
