aboutsummaryrefslogtreecommitdiff
path: root/i386/pc/rv86/rv86_real_int.c
diff options
context:
space:
mode:
authorThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
committerThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
commitf07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch)
tree12b07c7e578fc1a5f53dbfde2632408491ff2a70 /i386/pc/rv86/rv86_real_int.c
downloadgnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.tar.gz
gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.tar.bz2
gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.zip
Initial source
Diffstat (limited to 'i386/pc/rv86/rv86_real_int.c')
-rw-r--r--i386/pc/rv86/rv86_real_int.c276
1 files changed, 276 insertions, 0 deletions
diff --git a/i386/pc/rv86/rv86_real_int.c b/i386/pc/rv86/rv86_real_int.c
new file mode 100644
index 00000000..d9c35b68
--- /dev/null
+++ b/i386/pc/rv86/rv86_real_int.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 1995-1994 The University of Utah and
+ * the Computer Systems Laboratory at the University of Utah (CSL).
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify and distribute this software is hereby
+ * granted provided that (1) source code retains these copyright, permission,
+ * and disclaimer notices, and (2) redistributions including binaries
+ * reproduce the notices in supporting documentation, and (3) all advertising
+ * materials mentioning features or use of this software display the following
+ * acknowledgement: ``This product includes software developed by the
+ * Computer Systems Laboratory at the University of Utah.''
+ *
+ * THE UNIVERSITY OF UTAH AND CSL ALLOW FREE USE OF THIS SOFTWARE IN ITS "AS
+ * IS" CONDITION. THE UNIVERSITY OF UTAH AND CSL DISCLAIM ANY LIABILITY OF
+ * ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ *
+ * CSL requests users of this software to return to csl-dist@cs.utah.edu any
+ * improvements that they make and grant CSL redistribution rights.
+ *
+ * Author: Bryan Ford, University of Utah CSL
+ */
+
+#include <mach/machine/seg.h>
+#include <mach/machine/proc_reg.h>
+#include <mach/machine/far_ptr.h>
+#include <mach/machine/eflags.h>
+
+#include "vm_param.h"
+#include "real.h"
+#include "real_tss.h"
+#include "cpu.h"
+#include "debug.h"
+
+
+/*
+
+ There seem to be three main ways to handle v86 mode:
+
+ * The v86 environment is just an extension of the normal kernel environment:
+ you can switch to and from v86 mode just as you can change any other processor state.
+ You always keep running on the separate "logical" stack,
+ which is the kernel stack when running in protected mode,
+ or the user stack when running in v86 mode.
+ When in v86 mode, the "actual" kernel stack is just a stub
+ big enough to switch back to the "normal" kernel stack,
+ which was being used as the user stack while running in v86 mode.
+ Thus, v86 and protected-mode "segments" of stack data
+ can be interleaved together on the same logical stack.
+
+ - To make a real int call from kernel pmode,
+ switch to v86 mode and execute an int instruction,
+ then switch back to protected mode.
+
+ - To reflect an interrupt to v86 mode:
+
+ > If the processor was running in v86 mode,
+ just adjust the kernel and user stacks
+ to emulate a real-mode interrupt, and return.
+
+ > If the processor was running in pmode,
+ switch to v86 mode and re-trigger the interrupt
+ with a software int instruction.
+
+ - To handle an interrupt in pmode:
+
+ > If the processor was running in v86 mode,
+ switch from the stub stack to the user stack that was in use
+ (could be different from the stack we set originally,
+ because BIOS/DOS code might have switched stacks!),
+ call the interrupt handler, switch back, and return.
+
+ > If the processor was running in pmode,
+ just call the interrupt handler and return.
+
+ This method only works if the whole "kernel" is <64KB
+ and generally compatible with real-mode execution.
+ This is the model my DOS extender currently uses.
+
+ One major disadvantage of this method
+ is that interrupt handlers can't run "general" protected-mode code,
+ such as typical code compiled by GCC.
+ This is because, if an interrupt occurs while in v86 mode,
+ the v86-mode ss:sp may point basically anywhere in the low 1MB,
+ and it therefore it can't be used directly as a pmode stack;
+ and the only other stack available is the miniscule stub stack.
+ Since "general" protected-mode code expects a full-size stack
+ with an SS equal to the normal protected-mode DS,
+ neither of these available stacks will suffice.
+ It is impossible to switch back to the original kernel stack
+ because arbitrary DOS or BIOS code might have switched from it
+ to a different stack somewhere else in the low 1MB,
+ and we have no way of telling where the SP was when that happened.
+ The upshot is that interrupt handlers must be extremely simple;
+ in MOSS, all they do is post a signal to "the process,"
+ and return immediately without actually handling the interrupt.
+
+ * The v86 environment is a separate "task" with its own user and kernel stacks;
+ you switch back and forth as if between multiple ordinary tasks,
+ the tasks can preempt each other, go idle waiting for events, etc.
+
+ - To make a real int call from kernel pmode,
+ the task making the call essentially does a synchronous IPC to the v86 task.
+ If the v86 task is busy with another request or a reflected interrupt,
+ the calling task will go idle until the v86 task is available.
+
+ - Reflecting an interrupt to v86 mode
+ basically amounts to sending a Unix-like "signal" to the v86 task:
+
+ > If the processor was running in the v86 task,
+ just adjust the kernel and user stacks
+ to emulate a real-mode interrupt, and return.
+
+ > If the processor was running in a protected-mode task
+ (or another v86-mode task),
+ post a signal to the v86 task, wake it up if it's asleep,
+ and invoke the scheduler to switch to the v86 task
+ if it has a higher priority than the currently running task.
+
+ - To handle an interrupt in pmode,
+ just call the interrupt handler and return.
+ It doesn't matter whether the interrupt was from v86 or pmode,
+ because the kernel stacks look the same in either case.
+
+ One big problem with this method is that if interrupts are to be handled in v86 mode,
+ all the typical problems of handling interrupts in user-mode tasks pop up.
+ In particular, an interrupt can now cause preemption,
+ so this will break an interruptible but nonpreemptible environment.
+ (The problem is not that the interrupted task is "preempted"
+ to switch temporarily to the v86 task to handle the interrupt;
+ the problem is that when the v86 task is done handling the interrupt,
+ the scheduler will be invoked and some task other than the interrupted task may be run.)
+
+ Of course, this is undoubtedly the right solution
+ if that's the interrupt model the OS is using anyway
+ (i.e. if the OS already supports user-level protected-mode interrupts).
+
+ * A bastardization of the two above approaches:
+ treat the v86 environment as a separate "task",
+ but a special one that doesn't behave at all like other tasks.
+ The v86 "task" in this case is more of an "interrupt co-stack"
+ that grows and shrinks alongside the normal interrupt stack
+ (or the current kernel stack, if interrupts are handled on the kernel stack).
+ Interrupts and real calls can cause switches between these two interrupt stacks,
+ but they can't cause preemption in the normal sense.
+ The route taken while building the stacks is exactly the opposite
+ the route taken while tearing it down.
+
+ Now two "kernel stack pointers" have to be maintained all the time instead of one.
+ When running in protected mode:
+
+ - The ESP register contains the pmode stack pointer.
+ - Some global variable contains the v86 stack pointer.
+
+ When running in v86 mode:
+
+ - The ESP register contains the v86 stack pointer.
+ (Note that BIOS/DOS code can switch stacks,
+ so at any given time it may point practically anywhere!)
+ - The current tss's esp0 contains the pmode stack pointer.
+
+ Whenever a switch is made, a stack frame is placed on the new co-stack
+ indicating that the switch was performed.
+
+ - To make a real int call from kernel pmode,
+ build a real-mode interrupt stack frame on the v86 interrupt stack,
+ build a v86-mode trap stack frame on the pmode stack,
+ set the tss's esp0 to point to the end of that stack frame,
+ and iret from it.
+ Then when the magic "done-with-real-call" int instruction is hit,
+ the pmode interrupt handler will see it
+ and know to simply destroy the v86 trap stack on the pmode stack.
+
+ - Handling an interrupt can always be thought of as going "through" pmode:
+ switching from the v86 stack to the pmode stack
+ if the processor was in v86 mode when the interrupt was taken,
+ and switching from the pmode stack back to the v86 stack as described above
+ if the interrupt is to be reflected to v86 mode.
+
+ Of course, optimized paths are possible:
+
+ - To reflect an interrupt to v86 mode:
+
+ > If the processor was running in v86 mode,
+ just adjust the kernel and user stack frames and return.
+
+ > If the processor was running in pmode,
+ do as described above for explicit real int calls.
+
+ - To handle an interrupt in pmode:
+
+ > If the processor was running in v86 mode,
+ switch to the pmode stack,
+ stash the old v86 stack pointer variable on the pmode stack,
+ and set the v86 stack pointer variable to the new location.
+ Call the interrupt handler,
+ then tear down everything and return to v86 mode.
+
+ Observation:
+ In the first and third models,
+ explicit real int calls are entirely symmetrical
+ to hardware interrupts from pmode to v86 mode.
+ This is valid because of the interruptible but nonpreemptible model:
+ no scheduling is involved, and the stack(s) will always be torn down
+ in exactly the opposite order in which they were built up.
+ In the second model,
+ explicit real calls are quite different,
+ because the BIOS is interruptible but nonpreemptible:
+ you can reflect an interrupt into the v86 task at any time,
+ but you can only make an explicit request to that task when it's ready
+ (i.e. no other requests or interrupts are outstanding).
+
+*/
+
+
+
+#define RV86_USTACK_SIZE 1024
+
+vm_offset_t rv86_ustack_pa;
+vm_offset_t rv86_return_int_pa;
+struct far_pointer_32 rv86_usp;
+struct far_pointer_16 rv86_rp;
+
+void rv86_real_int(int intnum, struct real_call_data *rcd)
+{
+ unsigned short old_tr;
+ unsigned int old_eflags;
+
+ /* If this is the first time this routine is being called,
+ initialize the kernel stack. */
+ if (!rv86_ustack_pa)
+ {
+ rv86_ustack_pa = 0xa0000 - RV86_USTACK_SIZE; /* XXX */
+
+ assert(rv86_ustack_pa < 0x100000);
+
+ /* Use the top two bytes of the ustack for an 'int $0xff' instruction. */
+ rv86_return_int_pa = rv86_ustack_pa + RV86_USTACK_SIZE - 2;
+ *(short*)phystokv(rv86_return_int_pa) = 0xffcd;
+
+ /* Set up the v86 stack pointer. */
+ rv86_usp.seg = rv86_rp.seg = rv86_ustack_pa >> 4;
+ rv86_usp.ofs = rv86_rp.ofs = (rv86_ustack_pa & 0xf) + RV86_USTACK_SIZE - 2;
+
+ /* Pre-allocate a real-mode interrupt stack frame. */
+ rv86_usp.ofs -= 6;
+ }
+
+ /* Make sure interrupts are disabled. */
+ old_eflags = get_eflags();
+
+ /* Switch to the TSS to use in v86 mode. */
+ old_tr = get_tr();
+ cpu[0].tables.gdt[REAL_TSS_IDX].access &= ~ACC_TSS_BUSY;
+ set_tr(REAL_TSS);
+
+ asm volatile("
+ pushl %%ebp
+ pushl %%eax
+ call rv86_real_int_asm
+ popl %%eax
+ popl %%ebp
+ " :
+ : "a" (rcd), "S" (intnum)
+ : "eax", "ebx", "ecx", "edx", "esi", "edi");
+
+ /* Switch to the original TSS. */
+ cpu[0].tables.gdt[old_tr/8].access &= ~ACC_TSS_BUSY;
+ set_tr(old_tr);
+
+ /* Restore the original processor flags. */
+ set_eflags(old_eflags);
+}
+
+void (*real_int)(int intnum, struct real_call_data *rcd) = rv86_real_int;
+