diff options
author | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
---|---|---|
committer | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
commit | f07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch) | |
tree | 12b07c7e578fc1a5f53dbfde2632408491ff2a70 /i386/pc/rv86/rv86_real_int.c | |
download | gnumach-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.c | 276 |
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; + |