summaryrefslogtreecommitdiff
path: root/i386/pc/rv86/rv86_real_int.c
blob: d9c35b68596c9da8b0aab3a288d991d3303649e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
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;