It handles transitions to and from protected mode
  interrupt processing of real mode routines while in protected mode
  and vice versa,
  PIC translations from real mode to protected mode for hardware interrupts
  interrupt chaining

Operation:
  The basic idea is to shift into 386 mode completely.  We want to continue
  to use BIOS and DOS for I/O which means there has to be a provision for
  going back to real mode long enough to execute the real-mode code and then
  switch back to the 386 32 bit protected mode.  Either I had to open up 
  a virtual 8086 task while in protected mode or I had to make things 
  so you could switch between real mode and protected mode at a whim.  
  I was actually really interested in making a virtual task by the way...
  but the major stumbling block is I can't just  map in DOS and the BIOS 
  on the machine I'm on because the BIOS at least uses some 32 bit and 
  386 specific real-mode instructions and I don't know if these are supported
  in a virtual task.
    But back to the subject at hand.  Interrupts ar the main thing that makes
  this code complex.  Just think, if you have interrupts running and
  you're not entirely sure when they are going to hit but you could be in
  either protected mode or real mode when it does... then if you want to
  run an 80386 protected mode interrupt you have to make sure it will 
  run when the CPU is in real mode.  More to the point, there are tons of
  real mode interrupts already running on the 386 but the whole point of this
  exercise is you want to run in protected mode and still have the bios and
  dos function.  They depend on these interrupts running.  So what happens
  when an interrupt that has to go to the bios hits when you're in protected
  mode trotting along?
    The answer to these and other mysterious questions... is the reason this
  program got so complex.  Simply put, if you're in the wrong CPU mode
  when an interrupt hits you have to switch to the correct CPU mode long enough
  for the interrupt to run and then switch back so the program can run
  some more.  But if you're in the correct CPU mode when the interrupt hits
  everything just runs according to spec, i.e. the interrupt gate is taken
  or the real mode interrupt vector is taken.
    We handle this by having two interrupt tables.  The standard 8086
  vector table is one, and we create a protected mode IDT as the other.  For each
  hardware interrupt we fill in an entry in both tables.  When the processor
  is in real mode the standard 8086 table is accessed and the interrupt is
  run.  When the processor is in protected mode the 386 interrupt gate is
  taken and 386 code is executed.  So now if we want to run an 8086 interrupt
  we fill in the 8086 table to point directly at the interrupt code; we fill
  in the 386 table to point to appropriate code to make the protected-to-real
  transition long enough to run the interrupt.  On the other hand, if
  we want to write a 386 protected mode interrupt we set up the standard
  386 gate to point to the code and set up the 8086 real-mode vector to point
  to transition code to get us into protected mode long enough to run the
  interrupt and then transition back to real mode.
    Real-to-protected and protected-to-real mode transitions come a lot of
  different ways and some of the code you see is meant to handle them.
    We have one other problem; we have to play with the PIC.  To remap the
  hardware interrupts to a different place; the PIC 0 interrupts (timer,
  keyboard, video, serial, disk) in particular collide with certain 386
  exceptions we are going to trap.  The code does by the way bomb out cleanly
  if an exception is triggered.  We map hardware interrupts in the range:

    standard    New
	
    8-0f	80h to 87h
    70h-77h	88h to 8fh

    The new values are used as long as the program is executing and then
  the standard values are restored.

Transitions:
  The basic strategy for the real to protected mode transition is:
    1) Load the GDT and IDT for protected mode
    2) Switch the protected mode via bit 0 of CR0
    3) Load the segment registers with protected mode values in such
       a way that the the CS:IP combination continues along the same thread.
    4) Convert the stack pointer to real mode

  The basic strategy for the protected-to-real mode transition is:
    1) Load all segment registers with 16 bit dos-compatible segment
       selectors in such a way that the CS:IP combination maintains integrity
    2) Fiddle with the stack to get us a real mode pointer, but leave at
       least 256 bytes available to the real-mode code.
    3) Switch to real mode
    4) Load all segment registers with real mode segment values

  To run a bios or dos routine after 386 mode is up and running
    1) Switch to real mode
    2) Run the routine
    3) Switch back to protected mode.

  To run a real mode interrupt
    1) Switch to real mode if not already there
    2) Run the interrupt
    3) Switch to protected mode if we came out to run the interrupt

  To run a protected mode interrupt
    1) Switch to protected mode if not already there
    2) Run the interrupt
    3) Switch to real mode if we had to come into protected mode


  


  During compile we:
      Create a gdt description table which will be parsed to create the
    GDT.  Leave GDT space for the data from this description table along with
    extra space for user-defined GDT entries.  The only GDTs implemented
    are 386 code and data segments, which have a base address beginning
    in whatever real-mode segment the program starts.  An extra GDT has a
    base address at absolute zero so you can access memory with the same
    addresses as DOS uses if you want.  Two other GDTs exist as 16 bit
    real-mode compatible protected mode segments.
      Create a variable called zero to help us during transitions or any
    time we want to find out how far a code or data address is offset from
    absolute zero.
      Create the protected mode IDT table and sufficient information to
    allow us to fill in the real mode IDT table as needed when the program
    comes up.
      The .DEF files are particularly important; they define the GDT/IDT
    environment the program will run in.

  During power up we:
      Create the GDT.  Load the GDT and IDT with protected mode values.
    This includes changing the GDT base addresses as appropriate so that
    the required segments will have the beginning of the program as their
    zero base.
      Fix 'zero' to be a 32 bit value containing the offset of the first
    segment in the probram.
      Switch to protected mode.
      Switch the PIC over to the new interrupt scheme.
      Run the user program
      Switch the PIC back to the old scheme
      Switch back to real mode
      Exit.

  It's that simple.  Well, almost.  I wrote a lot of generic macros which
  switch back and forth between data segments a lot.  Interrupt vectors
  are particularly troublesome; typcially if they occur in the wrong mode
  what happens is a register is loaded with the interrupt number and then
  a generic routine is called to handle the processor mode switch; the
  interrupt is called as a subroutine, the mode is switched back, and we
  return to the task.  Code is not necessarily contiguous throughout a mode
  switch either; it's not obvious but the macros have it jumping back and
  forth between different code segments quite a bit.  And there are some
  magic numbers in the stack manipulation... the point there by the way is
  to set SS (real mode) in such a way that SP is between 256 and 263.  How
  much difference is there between 100h in SP and 10H in SS ??
     

Caveats:
  Interrupt code must reside in first 64K of program.  With some work
  protected mode interrupts could be moved out of first 64K.  With a lot
  more work real mode interrupts could be done this way as well I think;
  I haven't  worked on the code in years so I don't know if the
  extra work could be done by the assembler or would be a run-time 
  performance degradation right off hand.

  All segments are read/write.  In fact the interrupt handlers use a
  self-modifying code segment so this is mandatory.  Of course you can't
  set a protected mode code segment to be read/write, but then again
  interrupt vectors often find themself in 8086 mode.

  Lots of overhead on interrupt calls that occur when the processor is
  in the wrong mode due to the switchover mechanism.

  This Must have a 386 processor or better; it does not check to see 
  if one is available.

  
CPU.ASM is included as sample code to check processor type.  This is
  my version, and has obvious setbacks compared to other versions in the
  library.

David Lindauer
1428 Hepburn Ave.
Louisville, KY 40204