I think Tim's tutorial is pretty good, too, but actually he makes it more difficult than it needs to be. Just stuffing your normal code's TSS segment descriptor into the GPF's vector in the IDT allows you to bounce in and out of V86 mode using a threaded coding style, examining the various fields of the TSS for register values and such; much easier than trying to write a GPF interrupt handler state-machine style. Performing a task switch for each V86 monitor operation is probably a little slower, but not noticably so.
Here's my own V86 monitor, written multitasking style and with support for a couple of opcodes that were missing from Tim's tutorial IIRC:
Code:
INLINE void v86push16(UInt16 x)
{
v86.tss.esp = (UInt16)(v86.tss.esp - 2);
*(UInt16*)(v86.tss.ss * 16 + v86.tss.esp) = x;
}
INLINE void v86push32(UInt32 x)
{
v86.tss.esp = (UInt16)(v86.tss.esp - 4);
*(UInt32*)(v86.tss.ss * 16 + v86.tss.esp) = x;
}
INLINE UInt16 v86pop16(void)
{
UInt16 result = *(UInt16*)(v86.tss.ss * 16 + v86.tss.esp);
v86.tss.esp = (UInt16)(v86.tss.esp + 2);
return result;
}
INLINE UInt32 v86pop32(void)
{
UInt32 result = *(UInt32*)(v86.tss.ss * 16 + v86.tss.esp);
v86.tss.esp = (UInt16)(v86.tss.esp + 4);
return result;
}
void callV86(UInt16 segment, UInt16 offset)
{
int o32 = 0, a32 = 0;
UInt32 err;
v86.tss.cs = segment;
v86.tss.eip = offset;
v86.tss.eflags = (v86.tss.eflags & EFLAGS_REAL) | EFLAGS_VM;
disablePreemption(currentTask); // disallow multitasking for the moment
setTaskHandler(0x0D, currentTask->selector, 0); // this task is GPF handler
ljmp(v86selector); // switch to V86 task
err = pop(); // pop GPF error code
for (;;)
{
UInt8 *ip = (UInt8*)(v86.tss.cs * 16 + v86.tss.eip);
switch ( *ip )
{
case 0x66: // O32
o32 = 1;
v86.tss.eip = (UInt16)(v86.tss.eip + 1);
continue;
case 0x67: // A32
a32 = 1;
v86.tss.eip = (UInt16)(v86.tss.eip + 1);
continue;
case 0x9C: // PUSHF
if ( o32 )
v86push32(v86.tss.eflags);
else
v86push16(v86.tss.eflags);
v86.tss.eip = (UInt16)(v86.tss.eip + 1);
break;
case 0x9D: // POPF
if ( o32 )
v86.tss.eflags = (v86pop32() & EFLAGS_REAL) | EFLAGS_VM;
else
v86.tss.eflags = (v86pop16() & EFLAGS_REAL) | EFLAGS_VM;
v86.tss.eip = (UInt16)(v86.tss.eip + 1);
break;
case 0xCC: // INT 3
case 0xCD: // INT x
v86push16(v86.tss.eflags);
v86push16(v86.tss.cs);
v86push16(v86.tss.eip + (ip[0] - 0xCB));
v86.tss.eip = *(UInt16*)(ip[0] == 0xCC ? 12 : ip[1] * 4);
v86.tss.cs = *(UInt16*)(ip[0] == 0xCC ? 14 : ip[1] * 4 + 2);
v86.tss.eflags &= ~(EFLAGS_IF | EFLAGS_TF);
break;
case 0xCF: // IRET
v86.tss.eip = v86pop16();
v86.tss.cs = v86pop16();
v86.tss.eflags = (v86pop16() & EFLAGS_REAL) | EFLAGS_VM;
break;
default:
dprintf("V86 unknown opcode: %02X (err=%04X)\n", *ip, (UInt16)err);
dumpTSS(&v86.tss);
// The ability in C to fall through into the next case is an
// outstanding feature of the language, not a design flaw.
case 0xF4: // HLT
setEFLAGS(getEFLAGS() & ~EFLAGS_NT); // clear Nested Task flag
resetTaskSelector(v86selector); // clear busy bit on V86 task
setIntHandler(0x0D, handler0D); // reset standard GPF handler
enablePreemption(currentTask); // reenable multitasking
return; // ...and I am outta here...
case 0xFA: // CLI
v86.tss.eflags &= ~EFLAGS_IF;
v86.tss.eip = (UInt16)(v86.tss.eip + 1);
break;
case 0xFB: // STI
v86.tss.eflags |= EFLAGS_IF;
v86.tss.eip = (UInt16)(v86.tss.eip + 1);
break;
}
iret(); // return to V86 task
err = pop(); // pop GPF error code off stack
o32 = a32 = 0;
}
}