|
|
1.1 root 1: /*
2: * Copyright (c) 1995-1994 The University of Utah and
3: * the Computer Systems Laboratory at the University of Utah (CSL).
4: * All rights reserved.
5: *
6: * Permission to use, copy, modify and distribute this software is hereby
7: * granted provided that (1) source code retains these copyright, permission,
8: * and disclaimer notices, and (2) redistributions including binaries
9: * reproduce the notices in supporting documentation, and (3) all advertising
10: * materials mentioning features or use of this software display the following
11: * acknowledgement: ``This product includes software developed by the
12: * Computer Systems Laboratory at the University of Utah.''
13: *
14: * THE UNIVERSITY OF UTAH AND CSL ALLOW FREE USE OF THIS SOFTWARE IN ITS "AS
15: * IS" CONDITION. THE UNIVERSITY OF UTAH AND CSL DISCLAIM ANY LIABILITY OF
16: * ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
17: *
18: * CSL requests users of this software to return to [email protected] any
19: * improvements that they make and grant CSL redistribution rights.
20: *
21: * Author: Bryan Ford, University of Utah CSL
22: */
23:
24: #include <mach/machine/seg.h>
25: #include <mach/machine/proc_reg.h>
26: #include <mach/machine/far_ptr.h>
27: #include <mach/machine/eflags.h>
28:
29: #include "vm_param.h"
30: #include "real.h"
31: #include "real_tss.h"
32: #include "cpu.h"
33: #include "debug.h"
34:
35:
36: /*
37:
38: There seem to be three main ways to handle v86 mode:
39:
40: * The v86 environment is just an extension of the normal kernel environment:
41: you can switch to and from v86 mode just as you can change any other processor state.
42: You always keep running on the separate "logical" stack,
43: which is the kernel stack when running in protected mode,
44: or the user stack when running in v86 mode.
45: When in v86 mode, the "actual" kernel stack is just a stub
46: big enough to switch back to the "normal" kernel stack,
47: which was being used as the user stack while running in v86 mode.
48: Thus, v86 and protected-mode "segments" of stack data
49: can be interleaved together on the same logical stack.
50:
51: - To make a real int call from kernel pmode,
52: switch to v86 mode and execute an int instruction,
53: then switch back to protected mode.
54:
55: - To reflect an interrupt to v86 mode:
56:
57: > If the processor was running in v86 mode,
58: just adjust the kernel and user stacks
59: to emulate a real-mode interrupt, and return.
60:
61: > If the processor was running in pmode,
62: switch to v86 mode and re-trigger the interrupt
63: with a software int instruction.
64:
65: - To handle an interrupt in pmode:
66:
67: > If the processor was running in v86 mode,
68: switch from the stub stack to the user stack that was in use
69: (could be different from the stack we set originally,
70: because BIOS/DOS code might have switched stacks!),
71: call the interrupt handler, switch back, and return.
72:
73: > If the processor was running in pmode,
74: just call the interrupt handler and return.
75:
76: This method only works if the whole "kernel" is <64KB
77: and generally compatible with real-mode execution.
78: This is the model my DOS extender currently uses.
79:
80: One major disadvantage of this method
81: is that interrupt handlers can't run "general" protected-mode code,
82: such as typical code compiled by GCC.
83: This is because, if an interrupt occurs while in v86 mode,
84: the v86-mode ss:sp may point basically anywhere in the low 1MB,
85: and it therefore it can't be used directly as a pmode stack;
86: and the only other stack available is the miniscule stub stack.
87: Since "general" protected-mode code expects a full-size stack
88: with an SS equal to the normal protected-mode DS,
89: neither of these available stacks will suffice.
90: It is impossible to switch back to the original kernel stack
91: because arbitrary DOS or BIOS code might have switched from it
92: to a different stack somewhere else in the low 1MB,
93: and we have no way of telling where the SP was when that happened.
94: The upshot is that interrupt handlers must be extremely simple;
95: in MOSS, all they do is post a signal to "the process,"
96: and return immediately without actually handling the interrupt.
97:
98: * The v86 environment is a separate "task" with its own user and kernel stacks;
99: you switch back and forth as if between multiple ordinary tasks,
100: the tasks can preempt each other, go idle waiting for events, etc.
101:
102: - To make a real int call from kernel pmode,
103: the task making the call essentially does a synchronous IPC to the v86 task.
104: If the v86 task is busy with another request or a reflected interrupt,
105: the calling task will go idle until the v86 task is available.
106:
107: - Reflecting an interrupt to v86 mode
108: basically amounts to sending a Unix-like "signal" to the v86 task:
109:
110: > If the processor was running in the v86 task,
111: just adjust the kernel and user stacks
112: to emulate a real-mode interrupt, and return.
113:
114: > If the processor was running in a protected-mode task
115: (or another v86-mode task),
116: post a signal to the v86 task, wake it up if it's asleep,
117: and invoke the scheduler to switch to the v86 task
118: if it has a higher priority than the currently running task.
119:
120: - To handle an interrupt in pmode,
121: just call the interrupt handler and return.
122: It doesn't matter whether the interrupt was from v86 or pmode,
123: because the kernel stacks look the same in either case.
124:
125: One big problem with this method is that if interrupts are to be handled in v86 mode,
126: all the typical problems of handling interrupts in user-mode tasks pop up.
127: In particular, an interrupt can now cause preemption,
128: so this will break an interruptible but nonpreemptible environment.
129: (The problem is not that the interrupted task is "preempted"
130: to switch temporarily to the v86 task to handle the interrupt;
131: the problem is that when the v86 task is done handling the interrupt,
132: the scheduler will be invoked and some task other than the interrupted task may be run.)
133:
134: Of course, this is undoubtedly the right solution
135: if that's the interrupt model the OS is using anyway
136: (i.e. if the OS already supports user-level protected-mode interrupts).
137:
138: * A bastardization of the two above approaches:
139: treat the v86 environment as a separate "task",
140: but a special one that doesn't behave at all like other tasks.
141: The v86 "task" in this case is more of an "interrupt co-stack"
142: that grows and shrinks alongside the normal interrupt stack
143: (or the current kernel stack, if interrupts are handled on the kernel stack).
144: Interrupts and real calls can cause switches between these two interrupt stacks,
145: but they can't cause preemption in the normal sense.
146: The route taken while building the stacks is exactly the opposite
147: the route taken while tearing it down.
148:
149: Now two "kernel stack pointers" have to be maintained all the time instead of one.
150: When running in protected mode:
151:
152: - The ESP register contains the pmode stack pointer.
153: - Some global variable contains the v86 stack pointer.
154:
155: When running in v86 mode:
156:
157: - The ESP register contains the v86 stack pointer.
158: (Note that BIOS/DOS code can switch stacks,
159: so at any given time it may point practically anywhere!)
160: - The current tss's esp0 contains the pmode stack pointer.
161:
162: Whenever a switch is made, a stack frame is placed on the new co-stack
163: indicating that the switch was performed.
164:
165: - To make a real int call from kernel pmode,
166: build a real-mode interrupt stack frame on the v86 interrupt stack,
167: build a v86-mode trap stack frame on the pmode stack,
168: set the tss's esp0 to point to the end of that stack frame,
169: and iret from it.
170: Then when the magic "done-with-real-call" int instruction is hit,
171: the pmode interrupt handler will see it
172: and know to simply destroy the v86 trap stack on the pmode stack.
173:
174: - Handling an interrupt can always be thought of as going "through" pmode:
175: switching from the v86 stack to the pmode stack
176: if the processor was in v86 mode when the interrupt was taken,
177: and switching from the pmode stack back to the v86 stack as described above
178: if the interrupt is to be reflected to v86 mode.
179:
180: Of course, optimized paths are possible:
181:
182: - To reflect an interrupt to v86 mode:
183:
184: > If the processor was running in v86 mode,
185: just adjust the kernel and user stack frames and return.
186:
187: > If the processor was running in pmode,
188: do as described above for explicit real int calls.
189:
190: - To handle an interrupt in pmode:
191:
192: > If the processor was running in v86 mode,
193: switch to the pmode stack,
194: stash the old v86 stack pointer variable on the pmode stack,
195: and set the v86 stack pointer variable to the new location.
196: Call the interrupt handler,
197: then tear down everything and return to v86 mode.
198:
199: Observation:
200: In the first and third models,
201: explicit real int calls are entirely symmetrical
202: to hardware interrupts from pmode to v86 mode.
203: This is valid because of the interruptible but nonpreemptible model:
204: no scheduling is involved, and the stack(s) will always be torn down
205: in exactly the opposite order in which they were built up.
206: In the second model,
207: explicit real calls are quite different,
208: because the BIOS is interruptible but nonpreemptible:
209: you can reflect an interrupt into the v86 task at any time,
210: but you can only make an explicit request to that task when it's ready
211: (i.e. no other requests or interrupts are outstanding).
212:
213: */
214:
215:
216:
217: #define RV86_USTACK_SIZE 1024
218:
219: vm_offset_t rv86_ustack_pa;
220: vm_offset_t rv86_return_int_pa;
221: struct far_pointer_32 rv86_usp;
222: struct far_pointer_16 rv86_rp;
223:
224: void rv86_real_int(int intnum, struct real_call_data *rcd)
225: {
226: unsigned short old_tr;
227: unsigned int old_eflags;
228:
229: /* If this is the first time this routine is being called,
230: initialize the kernel stack. */
231: if (!rv86_ustack_pa)
232: {
233: rv86_ustack_pa = 0xa0000 - RV86_USTACK_SIZE; /* XXX */
234:
235: assert(rv86_ustack_pa < 0x100000);
236:
237: /* Use the top two bytes of the ustack for an 'int $0xff' instruction. */
238: rv86_return_int_pa = rv86_ustack_pa + RV86_USTACK_SIZE - 2;
239: *(short*)phystokv(rv86_return_int_pa) = 0xffcd;
240:
241: /* Set up the v86 stack pointer. */
242: rv86_usp.seg = rv86_rp.seg = rv86_ustack_pa >> 4;
243: rv86_usp.ofs = rv86_rp.ofs = (rv86_ustack_pa & 0xf) + RV86_USTACK_SIZE - 2;
244:
245: /* Pre-allocate a real-mode interrupt stack frame. */
246: rv86_usp.ofs -= 6;
247: }
248:
249: /* Make sure interrupts are disabled. */
250: old_eflags = get_eflags();
251:
252: /* Switch to the TSS to use in v86 mode. */
253: old_tr = get_tr();
254: cpu[0].tables.gdt[REAL_TSS_IDX].access &= ~ACC_TSS_BUSY;
255: set_tr(REAL_TSS);
256:
257: asm volatile("
258: pushl %%ebp
259: pushl %%eax
260: call rv86_real_int_asm
261: popl %%eax
262: popl %%ebp
263: " :
264: : "a" (rcd), "S" (intnum)
265: : "eax", "ebx", "ecx", "edx", "esi", "edi");
266:
267: /* Switch to the original TSS. */
268: cpu[0].tables.gdt[old_tr/8].access &= ~ACC_TSS_BUSY;
269: set_tr(old_tr);
270:
271: /* Restore the original processor flags. */
272: set_eflags(old_eflags);
273: }
274:
275: void (*real_int)(int intnum, struct real_call_data *rcd) = rv86_real_int;
276:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.