Blame view

arch/x86/math-emu/get_address.c 10.5 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /*---------------------------------------------------------------------------+
   |  get_address.c                                                            |
   |                                                                           |
   | Get the effective address from an FPU instruction.                        |
   |                                                                           |
   | Copyright (C) 1992,1993,1994,1997                                         |
   |                       W. Metzenthen, 22 Parker St, Ormond, Vic 3163,      |
   |                       Australia.  E-mail   billm@suburbia.net             |
   |                                                                           |
   |                                                                           |
   +---------------------------------------------------------------------------*/
  
  /*---------------------------------------------------------------------------+
   | Note:                                                                     |
   |    The file contains code which accesses user memory.                     |
   |    Emulator static data may change when user memory is accessed, due to   |
   |    other processes using the emulator while swapping is in progress.      |
   +---------------------------------------------------------------------------*/
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
19
20
21
22
23
24
25
26
  #include <linux/stddef.h>
  
  #include <asm/uaccess.h>
  #include <asm/desc.h>
  
  #include "fpu_system.h"
  #include "exception.h"
  #include "fpu_emu.h"
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
27
28
29
  #define FPU_WRITE_BIT 0x10
  
  static int reg_offset[] = {
d315760ff   Tejun Heo   x86: fix math_emu...
30
31
32
33
34
35
36
37
  	offsetof(struct pt_regs, ax),
  	offsetof(struct pt_regs, cx),
  	offsetof(struct pt_regs, dx),
  	offsetof(struct pt_regs, bx),
  	offsetof(struct pt_regs, sp),
  	offsetof(struct pt_regs, bp),
  	offsetof(struct pt_regs, si),
  	offsetof(struct pt_regs, di)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
38
  };
d315760ff   Tejun Heo   x86: fix math_emu...
39
  #define REG_(x) (*(long *)(reg_offset[(x)] + (u_char *)FPU_info->regs))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
40
41
  
  static int reg_offset_vm86[] = {
d315760ff   Tejun Heo   x86: fix math_emu...
42
43
44
45
46
47
48
  	offsetof(struct pt_regs, cs),
  	offsetof(struct kernel_vm86_regs, ds),
  	offsetof(struct kernel_vm86_regs, es),
  	offsetof(struct kernel_vm86_regs, fs),
  	offsetof(struct kernel_vm86_regs, gs),
  	offsetof(struct pt_regs, ss),
  	offsetof(struct kernel_vm86_regs, ds)
3d0d14f98   Ingo Molnar   x86: lindent arch...
49
  };
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
50
51
  
  #define VM86_REG_(x) (*(unsigned short *) \
d315760ff   Tejun Heo   x86: fix math_emu...
52
  		(reg_offset_vm86[((unsigned)x)] + (u_char *)FPU_info->regs))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
53

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
54
  static int reg_offset_pm[] = {
d315760ff   Tejun Heo   x86: fix math_emu...
55
56
57
58
59
60
61
  	offsetof(struct pt_regs, cs),
  	offsetof(struct pt_regs, ds),
  	offsetof(struct pt_regs, es),
  	offsetof(struct pt_regs, fs),
  	offsetof(struct pt_regs, ds),	/* dummy, not saved on stack */
  	offsetof(struct pt_regs, ss),
  	offsetof(struct pt_regs, ds)
3d0d14f98   Ingo Molnar   x86: lindent arch...
62
  };
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
63
64
  
  #define PM_REG_(x) (*(unsigned short *) \
d315760ff   Tejun Heo   x86: fix math_emu...
65
  		(reg_offset_pm[((unsigned)x)] + (u_char *)FPU_info->regs))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
66

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
67
68
69
  /* Decode the SIB byte. This function assumes mod != 0 */
  static int sib(int mod, unsigned long *fpu_eip)
  {
3d0d14f98   Ingo Molnar   x86: lindent arch...
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
  	u_char ss, index, base;
  	long offset;
  
  	RE_ENTRANT_CHECK_OFF;
  	FPU_code_access_ok(1);
  	FPU_get_user(base, (u_char __user *) (*fpu_eip));	/* The SIB byte */
  	RE_ENTRANT_CHECK_ON;
  	(*fpu_eip)++;
  	ss = base >> 6;
  	index = (base >> 3) & 7;
  	base &= 7;
  
  	if ((mod == 0) && (base == 5))
  		offset = 0;	/* No base register */
  	else
  		offset = REG_(base);
  
  	if (index == 4) {
  		/* No index register */
  		/* A non-zero ss is illegal */
  		if (ss)
  			EXCEPTION(EX_Invalid);
  	} else {
  		offset += (REG_(index)) << ss;
  	}
  
  	if (mod == 1) {
  		/* 8 bit signed displacement */
  		long displacement;
  		RE_ENTRANT_CHECK_OFF;
  		FPU_code_access_ok(1);
  		FPU_get_user(displacement, (signed char __user *)(*fpu_eip));
  		offset += displacement;
  		RE_ENTRANT_CHECK_ON;
  		(*fpu_eip)++;
  	} else if (mod == 2 || base == 5) {	/* The second condition also has mod==0 */
  		/* 32 bit displacement */
  		long displacement;
  		RE_ENTRANT_CHECK_OFF;
  		FPU_code_access_ok(4);
  		FPU_get_user(displacement, (long __user *)(*fpu_eip));
  		offset += displacement;
  		RE_ENTRANT_CHECK_ON;
  		(*fpu_eip) += 4;
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
115

3d0d14f98   Ingo Molnar   x86: lindent arch...
116
117
  	return offset;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
118

3d0d14f98   Ingo Molnar   x86: lindent arch...
119
  static unsigned long vm86_segment(u_char segment, struct address *addr)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
120
  {
3d0d14f98   Ingo Molnar   x86: lindent arch...
121
  	segment--;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
122
  #ifdef PARANOID
3d0d14f98   Ingo Molnar   x86: lindent arch...
123
124
125
126
  	if (segment > PREFIX_SS_) {
  		EXCEPTION(EX_INTERNAL | 0x130);
  		math_abort(FPU_info, SIGSEGV);
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
127
  #endif /* PARANOID */
3d0d14f98   Ingo Molnar   x86: lindent arch...
128
129
  	addr->selector = VM86_REG_(segment);
  	return (unsigned long)VM86_REG_(segment) << 4;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
131
132
133
  /* This should work for 16 and 32 bit protected mode. */
  static long pm_address(u_char FPU_modrm, u_char segment,
  		       struct address *addr, long offset)
3d0d14f98   Ingo Molnar   x86: lindent arch...
134
135
136
  {
  	struct desc_struct descriptor;
  	unsigned long base_address, limit, address, seg_top;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
137

3d0d14f98   Ingo Molnar   x86: lindent arch...
138
  	segment--;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
139
140
  
  #ifdef PARANOID
3d0d14f98   Ingo Molnar   x86: lindent arch...
141
142
143
144
145
  	/* segment is unsigned, so this also detects if segment was 0: */
  	if (segment > PREFIX_SS_) {
  		EXCEPTION(EX_INTERNAL | 0x132);
  		math_abort(FPU_info, SIGSEGV);
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
146
  #endif /* PARANOID */
3d0d14f98   Ingo Molnar   x86: lindent arch...
147
  	switch (segment) {
3d0d14f98   Ingo Molnar   x86: lindent arch...
148
  	case PREFIX_GS_ - 1:
d9a89a26e   Tejun Heo   x86: add %gs acce...
149
150
  		/* user gs handling can be lazy, use special accessors */
  		addr->selector = get_user_gs(FPU_info->regs);
3d0d14f98   Ingo Molnar   x86: lindent arch...
151
152
153
  		break;
  	default:
  		addr->selector = PM_REG_(segment);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
154
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
155

3d0d14f98   Ingo Molnar   x86: lindent arch...
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
  	descriptor = LDT_DESCRIPTOR(PM_REG_(segment));
  	base_address = SEG_BASE_ADDR(descriptor);
  	address = base_address + offset;
  	limit = base_address
  	    + (SEG_LIMIT(descriptor) + 1) * SEG_GRANULARITY(descriptor) - 1;
  	if (limit < base_address)
  		limit = 0xffffffff;
  
  	if (SEG_EXPAND_DOWN(descriptor)) {
  		if (SEG_G_BIT(descriptor))
  			seg_top = 0xffffffff;
  		else {
  			seg_top = base_address + (1 << 20);
  			if (seg_top < base_address)
  				seg_top = 0xffffffff;
  		}
  		access_limit =
  		    (address <= limit) || (address >= seg_top) ? 0 :
  		    ((seg_top - address) >= 255 ? 255 : seg_top - address);
  	} else {
  		access_limit =
  		    (address > limit) || (address < base_address) ? 0 :
  		    ((limit - address) >= 254 ? 255 : limit - address + 1);
  	}
  	if (SEG_EXECUTE_ONLY(descriptor) ||
  	    (!SEG_WRITE_PERM(descriptor) && (FPU_modrm & FPU_WRITE_BIT))) {
  		access_limit = 0;
  	}
  	return address;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
186
187
188
189
190
191
192
193
  
  /*
         MOD R/M byte:  MOD == 3 has a special use for the FPU
                        SIB byte used iff R/M = 100b
  
         7   6   5   4   3   2   1   0
         .....   .........   .........
          MOD    OPCODE(2)     R/M
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
194
195
196
197
198
199
200
201
202
         SIB byte
  
         7   6   5   4   3   2   1   0
         .....   .........   .........
          SS      INDEX        BASE
  
  */
  
  void __user *FPU_get_address(u_char FPU_modrm, unsigned long *fpu_eip,
3d0d14f98   Ingo Molnar   x86: lindent arch...
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
277
278
279
280
281
282
283
284
285
286
287
288
  			     struct address *addr, fpu_addr_modes addr_modes)
  {
  	u_char mod;
  	unsigned rm = FPU_modrm & 7;
  	long *cpu_reg_ptr;
  	int address = 0;	/* Initialized just to stop compiler warnings. */
  
  	/* Memory accessed via the cs selector is write protected
  	   in `non-segmented' 32 bit protected mode. */
  	if (!addr_modes.default_mode && (FPU_modrm & FPU_WRITE_BIT)
  	    && (addr_modes.override.segment == PREFIX_CS_)) {
  		math_abort(FPU_info, SIGSEGV);
  	}
  
  	addr->selector = FPU_DS;	/* Default, for 32 bit non-segmented mode. */
  
  	mod = (FPU_modrm >> 6) & 3;
  
  	if (rm == 4 && mod != 3) {
  		address = sib(mod, fpu_eip);
  	} else {
  		cpu_reg_ptr = &REG_(rm);
  		switch (mod) {
  		case 0:
  			if (rm == 5) {
  				/* Special case: disp32 */
  				RE_ENTRANT_CHECK_OFF;
  				FPU_code_access_ok(4);
  				FPU_get_user(address,
  					     (unsigned long __user
  					      *)(*fpu_eip));
  				(*fpu_eip) += 4;
  				RE_ENTRANT_CHECK_ON;
  				addr->offset = address;
  				return (void __user *)address;
  			} else {
  				address = *cpu_reg_ptr;	/* Just return the contents
  							   of the cpu register */
  				addr->offset = address;
  				return (void __user *)address;
  			}
  		case 1:
  			/* 8 bit signed displacement */
  			RE_ENTRANT_CHECK_OFF;
  			FPU_code_access_ok(1);
  			FPU_get_user(address, (signed char __user *)(*fpu_eip));
  			RE_ENTRANT_CHECK_ON;
  			(*fpu_eip)++;
  			break;
  		case 2:
  			/* 32 bit displacement */
  			RE_ENTRANT_CHECK_OFF;
  			FPU_code_access_ok(4);
  			FPU_get_user(address, (long __user *)(*fpu_eip));
  			(*fpu_eip) += 4;
  			RE_ENTRANT_CHECK_ON;
  			break;
  		case 3:
  			/* Not legal for the FPU */
  			EXCEPTION(EX_Invalid);
  		}
  		address += *cpu_reg_ptr;
  	}
  
  	addr->offset = address;
  
  	switch (addr_modes.default_mode) {
  	case 0:
  		break;
  	case VM86:
  		address += vm86_segment(addr_modes.override.segment, addr);
  		break;
  	case PM16:
  	case SEG32:
  		address = pm_address(FPU_modrm, addr_modes.override.segment,
  				     addr, address);
  		break;
  	default:
  		EXCEPTION(EX_INTERNAL | 0x133);
  	}
  
  	return (void __user *)address;
  }
  
  void __user *FPU_get_address_16(u_char FPU_modrm, unsigned long *fpu_eip,
  				struct address *addr, fpu_addr_modes addr_modes)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
289
  {
3d0d14f98   Ingo Molnar   x86: lindent arch...
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
  	u_char mod;
  	unsigned rm = FPU_modrm & 7;
  	int address = 0;	/* Default used for mod == 0 */
  
  	/* Memory accessed via the cs selector is write protected
  	   in `non-segmented' 32 bit protected mode. */
  	if (!addr_modes.default_mode && (FPU_modrm & FPU_WRITE_BIT)
  	    && (addr_modes.override.segment == PREFIX_CS_)) {
  		math_abort(FPU_info, SIGSEGV);
  	}
  
  	addr->selector = FPU_DS;	/* Default, for 32 bit non-segmented mode. */
  
  	mod = (FPU_modrm >> 6) & 3;
  
  	switch (mod) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
306
  	case 0:
3d0d14f98   Ingo Molnar   x86: lindent arch...
307
308
309
310
311
312
313
314
315
316
317
  		if (rm == 6) {
  			/* Special case: disp16 */
  			RE_ENTRANT_CHECK_OFF;
  			FPU_code_access_ok(2);
  			FPU_get_user(address,
  				     (unsigned short __user *)(*fpu_eip));
  			(*fpu_eip) += 2;
  			RE_ENTRANT_CHECK_ON;
  			goto add_segment;
  		}
  		break;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
318
  	case 1:
3d0d14f98   Ingo Molnar   x86: lindent arch...
319
320
321
322
323
324
325
  		/* 8 bit signed displacement */
  		RE_ENTRANT_CHECK_OFF;
  		FPU_code_access_ok(1);
  		FPU_get_user(address, (signed char __user *)(*fpu_eip));
  		RE_ENTRANT_CHECK_ON;
  		(*fpu_eip)++;
  		break;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
326
  	case 2:
3d0d14f98   Ingo Molnar   x86: lindent arch...
327
328
329
330
331
332
333
  		/* 16 bit displacement */
  		RE_ENTRANT_CHECK_OFF;
  		FPU_code_access_ok(2);
  		FPU_get_user(address, (unsigned short __user *)(*fpu_eip));
  		(*fpu_eip) += 2;
  		RE_ENTRANT_CHECK_ON;
  		break;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
334
  	case 3:
3d0d14f98   Ingo Molnar   x86: lindent arch...
335
336
337
338
339
340
  		/* Not legal for the FPU */
  		EXCEPTION(EX_Invalid);
  		break;
  	}
  	switch (rm) {
  	case 0:
d315760ff   Tejun Heo   x86: fix math_emu...
341
  		address += FPU_info->regs->bx + FPU_info->regs->si;
3d0d14f98   Ingo Molnar   x86: lindent arch...
342
343
  		break;
  	case 1:
d315760ff   Tejun Heo   x86: fix math_emu...
344
  		address += FPU_info->regs->bx + FPU_info->regs->di;
3d0d14f98   Ingo Molnar   x86: lindent arch...
345
346
  		break;
  	case 2:
d315760ff   Tejun Heo   x86: fix math_emu...
347
  		address += FPU_info->regs->bp + FPU_info->regs->si;
3d0d14f98   Ingo Molnar   x86: lindent arch...
348
349
350
351
  		if (addr_modes.override.segment == PREFIX_DEFAULT)
  			addr_modes.override.segment = PREFIX_SS_;
  		break;
  	case 3:
d315760ff   Tejun Heo   x86: fix math_emu...
352
  		address += FPU_info->regs->bp + FPU_info->regs->di;
3d0d14f98   Ingo Molnar   x86: lindent arch...
353
354
355
356
  		if (addr_modes.override.segment == PREFIX_DEFAULT)
  			addr_modes.override.segment = PREFIX_SS_;
  		break;
  	case 4:
d315760ff   Tejun Heo   x86: fix math_emu...
357
  		address += FPU_info->regs->si;
3d0d14f98   Ingo Molnar   x86: lindent arch...
358
359
  		break;
  	case 5:
d315760ff   Tejun Heo   x86: fix math_emu...
360
  		address += FPU_info->regs->di;
3d0d14f98   Ingo Molnar   x86: lindent arch...
361
362
  		break;
  	case 6:
d315760ff   Tejun Heo   x86: fix math_emu...
363
  		address += FPU_info->regs->bp;
3d0d14f98   Ingo Molnar   x86: lindent arch...
364
365
366
367
  		if (addr_modes.override.segment == PREFIX_DEFAULT)
  			addr_modes.override.segment = PREFIX_SS_;
  		break;
  	case 7:
d315760ff   Tejun Heo   x86: fix math_emu...
368
  		address += FPU_info->regs->bx;
3d0d14f98   Ingo Molnar   x86: lindent arch...
369
  		break;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
370
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
371

3d0d14f98   Ingo Molnar   x86: lindent arch...
372
373
        add_segment:
  	address &= 0xffff;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
374

3d0d14f98   Ingo Molnar   x86: lindent arch...
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
  	addr->offset = address;
  
  	switch (addr_modes.default_mode) {
  	case 0:
  		break;
  	case VM86:
  		address += vm86_segment(addr_modes.override.segment, addr);
  		break;
  	case PM16:
  	case SEG32:
  		address = pm_address(FPU_modrm, addr_modes.override.segment,
  				     addr, address);
  		break;
  	default:
  		EXCEPTION(EX_INTERNAL | 0x131);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
390
  	}
3d0d14f98   Ingo Molnar   x86: lindent arch...
391
392
  
  	return (void __user *)address;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
393
  }