   This document discusses several aspects of using the BIOS in V86 mode from
a pmode OS.  In doing so it refers to two examples I have built, v86test
and v86call.

1)  Actually calling V86 mode code from pmode is a small part of the project
of making the BIOS callable in V86 mode;  But I will discuss that part first.

   Calling V86 mode code from pmode is just a matter of creating a stack frame
that looks like the same stack frame that would have been created if V86 mode
code had been interrupted (just before the instruction you want to "call") and
then IRETing to the V86 mode code.

   When you IRET to V86 mode (or any outer ring) the CPU discards the current
value of the inner ring esp register.  When the outer ring code faults back to
the inner ring, the CPU reinitializes the inner ring esp from the TSS.  In
most designs, you must save and modify that field of the TSS as part of the
step of calling V86 mode.

   My v86call code takes advantage of the discarded esp to achieve a more
consistent interface between the C and ASM code.  I use a standard stack frame
that includes both the frame that the CPU creates on an interrupt from V86
mode, and the extra registers that all the pmode interrupt service routines
push when they are first entered.  I defined that stack frame as a structure
in both C and ASM.

   In order to call the BIOS, the C code declares one (or two) of those frame
structures (usually as a local variable on its own stack).  It fills in the
required registers and then passes a pointer to the frame to an ASM routine.
Since esp will be discarded anyway, the ASM code can simply disable interrupts
(which it must do for other reasons anyway) and then set esp equal to the
address of the structure.  It then uses the standard INT_EXIT macro to pop the
registers off the stack and return.

   My V86TEST code uses different frames in different situations to minimize
pushing and poping of registers that don't change and to avoid a few
situations in which frame structures might need to be copied from one place
to another.  This saves several cycles but introduces some very contorted
uses of the stack and prevents C from being used as the caller of the
routine that calls V86 mode.

2)  Returning from V86 mode to the kernel.

   Just as you can't really call V86 mode from the kernel (you must invert the
call into an IRET to get it done), you also can't really return from V86 mode
to the kernel.  You must invert the return into an interrupt or fault.

   In both versions, I use two different return points (for performance
reasons) and the two I use are int 41h, and int 46h.  Since I dedicate these
two interrupts to that purpose, I avoid the usual overhead ascociated with
using a GPF based return method.

   In a standard BIOS int 41h and int 46h are data pointers, not valid
interrupts.  Existing real mode code would never issue either of those
interrupts.  The IVT still exists as a data area used with the BIOS, but the
CPU doesn't use it for these interrupts.  Thus the IVT can contain the two
data pointers it normally contains without any conflict with the IDT containing
the interrupt gates for the two return points.

   I need 4 bytes of V86 mode memory in which to have the two INT instrctions.
In both versions I put them at linear 0x101000, which is FFFF:1010 in V86 mode.
In both versions I used tricks with my linker JLOC and my loader bootf so
that these four bytes simply land in the right place.  I don't need any extra
instructions during OS startup to write them there.

   Before calling the V86 mode code, the routines described in (1) modifies
the V86 mode stack so that an IRET (or a far ret) executed by the V86 mode
routine will reach the correct one of the return points.

   One of the return points is used when the V86 mode code cannot return any
information in any registers (such as a hardware interrupt).  In this case I
simply don't bother saving any of its registers.

   The other return point is used when the V86 mode code does return values.
My V86TEST versions uses stack tricks to get the registers saved in a usable
place with minimum CPU time.  This cannot all be done in the return handler;
There is a complexity burden placed on the original caller.

   My V86CALL version is simpler and slower (though still faster than typical
designs).  It builds a normal stack frame on entry to the return handler
(in stack space that must be freed before it returns).  Then it copies that
whole stack frame to a frame structure specified by the original caller.

   After doing whatever it needs to with the V86 registers, the return handler
must restore the esp0 field in the TSS, discard things off the stack, and
return to the caller of the v86_mode_caller routine.

3)  Servicing V86 mode.

   Most of the work of calling the BIOS in V86 mode is in handling the faults
and interrupts that occur while the V86 mode code is running.

   If you set IOPL to 3 (in the eflags register of the frame you build to call
V86 mode) then STI and CLI and IRET (and I forget what else) will work within
V86 mode.  Otherwise you get GPF's for those and then your GPF handler must be
able to detect and service those faults and continue execution of the V86
mode code.

   Even if you do set IOPL to 3, the CPU still checks IO permission bits in the
TSS for V86 mode code (unlike ring 3 pmode code, where IOPL=3 avoids that
check).  So you must set up that bitmask in your TSS.

   If you use V86 mode just for BIOS calls, both of the above are good ideas to
make things faster and simpler.  If you use the BIOS, you pretty much need to
trust the BIOS anyway.  For running V86 application code you don't want IOPL
set to 3 so you need the extra code to deal with V86 mode cli and sti etc.

   When the V86 mode code does an INT instruction, that interrupt must be
serviced in pmode (except there is an option on SOME CPUs to allow INTs to stay
in V86 mode.  I don't use that because I don't have those CPUs).

   Many of the hardware and software INTs that occur during V86 mode should be
serviced in V86 mode.  For example INT 13h waits for IRQ 6 (INT 0xE) to occur
in V86 mode to tell it that the floppy controller has finished something.
Many BIOS functions call the BIOS INT 15h for various reasons.

   My pmode service routines for those interrupts detect that the CPU was
running in V86 mode and simulate the interrupt for V86 mode.  This just
requires pushing three words on the V86 stack and changing cs,ip and flags in
the current stack frame.  That is faster and simpler than making a new frame to
call V86 mode and it also avoids the work of handling the return.

   My code initialized the 8259 so that IRQ0 to IRQ7 are INT 68h to INT 6Fh
instead of the usual INT 8h to INT 0Fh.  The V86 mode code still expects INT 8h
to INT 0Fh.  There is no difficulty there, but the concept seems to confuse
some people who have read about it.  For IRQ6, the 8259 will generate pmode
interrupt 6Eh.  The service routine for pmode interrupt 6Eh will simulate V86
mode interrupt 0Eh.  There is no overlap or conflict with pmode interrupt 0Eh
which is the page fault handler.

   Some hardware interrupts that occur during pmode should be serviced in V86
mode.  For example, if you try to use the CPU time while INT13h is waiting for
the floppy controller for pmode work, then the IRQ6 will happen in pmode rather
than in V86 mode.  My code doesn't yet try to use the CPU time during a floppy
wait, but it does use the BIOS INT16h, so IRQ1 must be directed to the BIOS
whenever it occurs.

   To redirect a hardware interrupt from pmode to V86 mode, you can't simply
use the same stack frame (as you can from V86 mode to V86 mode).  Instead you
need to create a new stack frame, like calling a V86 software interrupt from
pmode, but some of the work can be skipped because registers aren't passed to
and from the interrupt.

4)  Nested V86 calls.

   After the kernel calls a V86 mode routine, that V86 mode routine may call an
INT that requires pmode service.  That pmode routine may involve a software
or hardware interrupt that gets serviced back in V86 mode.

   In just using the BIOS from a pmode OS, there will be a few examples of
this and they won't get very deep.  If you want to run any application code
in V86 mode (like DOS programs) the nesting will be more common and deeper.

   Because of that nesting, you can't use a fixed address for the top of the
V86 mode stack and you can't leave a fixed value in the TSS to be used by the
CPU as the reinit point of the kernel stack.  The next section discusses
stacks in detail.

5)  Multiple stacks.

   You need a stack for the kernel code.  You need a stack for the V86 mode
code.  If you have ring 3 application code you need a stack for that too.
Because of nested calls, the stack you are switching to in any give operation
might not be empty.  A large part of the design complexity involves dealing
with that issue.

   One design would be to have several stacks and keep track of which are in
use and which aren't and always switch to the top of an available stack.  I
recently wasted a lot of time debugging a program that crashed because
Caldera DOS does something like that in its DPMI server and it (the Caldera
code) reused a stack that was still in use.  My V86 support code doesn't
use this method.

   Instead it has one big stack for each mode and it keeps track of how much has
been used in each of these stacks.  When it switches to a stack it switches to
just below what has already been used.

   All the work of keeping track takes just a little code which is only executed
when you add or remove a nesting level.  It needs a variable that contains the
esp reinit point for the most deeply nested transition from V86 mode.  If the
only nonkernel code in this task is the V86 code then this variable is simply
the esp0 field within the TSS.  (V86TEST works that way).  If there is also
nonkernel pmode code in the same task then the V86 copy of the kernel stack
reinit point must be a seperate variable and both it and the esp0 field must be
saved and restored together on nesting level changes.  (V86CALL will work that
way.)

   Starting from the kernel esp reinit point from the last V86 to kernel
transition you have the high end of the stack frame created by that transition.
In that frame you have the V86 ss and sp.  All pmode code that pushes things
on the V86 stack updates sp in the frame before using the space, so it is
always safe for hardware interrupts to nest into.

   Code that creates a new nesting level must set the esp0 field in the TSS to
the point in the kernel stack at which the return from V86 mode should occur.
In the V86CALL version it also sets the v86k_esp variable to the same value so
that if the V86 code is interrupted before it returns, it can be safely nested
further.

   The code that adds a nesting level does not check for the (common) special
case that nothing is pending.  In that case you want to start at the top of
the V86 stack.  To avoid the overhead of that check, the OS startup code
creates a dummy frame to pretend you are always nested inside of V86.  The only
thing used in that dummy frame is the ss,sp of the V86 top of stack.

6)  Multitasking.

   As written the code contains one TSS and assumes there is just one task.
How you adjust that depends on the relationship between V86 and mutitasking.
The minimum changes would occur if:
a)  You use TSS based multitasking
b)  Each task has its own address space containing its own kernel and V86
stacks, and its own v86k_esp and esp0 variables.
c)  All memory containing context for hardware interrupts is shared, but the
memory containing the V86 stack used for interrupts is not shared.

   You still need extra code to protect the BIOS software interrupts against
reentrant use, but most of the existing code can still be used.  BIOS hardware
interrupts need to protect themselves against reentrancy problems even in
single task real mode, so you can assume that isn't your problem.

   If some of the above choices are different, you may need to switch tasks as
part of revectoring, or you may no longer have strictly nested stack use and
may need a totally different method to manage the stacks.
