1/*---------------------------------------------------------------------------+
2 |  get_address.c                                                            |
3 |                                                                           |
4 | Get the effective address from an FPU instruction.                        |
5 |                                                                           |
6 | Copyright (C) 1992,1993,1994,1997                                         |
7 |                       W. Metzenthen, 22 Parker St, Ormond, Vic 3163,      |
8 |                       Australia.  E-mail   billm@suburbia.net             |
9 |                                                                           |
10 |                                                                           |
11 +---------------------------------------------------------------------------*/
12
13/*---------------------------------------------------------------------------+
14 | Note:                                                                     |
15 |    The file contains code which accesses user memory.                     |
16 |    Emulator static data may change when user memory is accessed, due to   |
17 |    other processes using the emulator while swapping is in progress.      |
18 +---------------------------------------------------------------------------*/
19
20#include <linux/stddef.h>
21
22#include <asm/uaccess.h>
23
24#include "fpu_system.h"
25#include "exception.h"
26#include "fpu_emu.h"
27
28#define FPU_WRITE_BIT 0x10
29
30static int reg_offset[] = {
31	offsetof(struct pt_regs, ax),
32	offsetof(struct pt_regs, cx),
33	offsetof(struct pt_regs, dx),
34	offsetof(struct pt_regs, bx),
35	offsetof(struct pt_regs, sp),
36	offsetof(struct pt_regs, bp),
37	offsetof(struct pt_regs, si),
38	offsetof(struct pt_regs, di)
39};
40
41#define REG_(x) (*(long *)(reg_offset[(x)] + (u_char *)FPU_info->regs))
42
43static int reg_offset_vm86[] = {
44	offsetof(struct pt_regs, cs),
45	offsetof(struct kernel_vm86_regs, ds),
46	offsetof(struct kernel_vm86_regs, es),
47	offsetof(struct kernel_vm86_regs, fs),
48	offsetof(struct kernel_vm86_regs, gs),
49	offsetof(struct pt_regs, ss),
50	offsetof(struct kernel_vm86_regs, ds)
51};
52
53#define VM86_REG_(x) (*(unsigned short *) \
54		(reg_offset_vm86[((unsigned)x)] + (u_char *)FPU_info->regs))
55
56static int reg_offset_pm[] = {
57	offsetof(struct pt_regs, cs),
58	offsetof(struct pt_regs, ds),
59	offsetof(struct pt_regs, es),
60	offsetof(struct pt_regs, fs),
61	offsetof(struct pt_regs, ds),	/* dummy, not saved on stack */
62	offsetof(struct pt_regs, ss),
63	offsetof(struct pt_regs, ds)
64};
65
66#define PM_REG_(x) (*(unsigned short *) \
67		(reg_offset_pm[((unsigned)x)] + (u_char *)FPU_info->regs))
68
69/* Decode the SIB byte. This function assumes mod != 0 */
70static int sib(int mod, unsigned long *fpu_eip)
71{
72	u_char ss, index, base;
73	long offset;
74
75	RE_ENTRANT_CHECK_OFF;
76	FPU_code_access_ok(1);
77	FPU_get_user(base, (u_char __user *) (*fpu_eip));	/* The SIB byte */
78	RE_ENTRANT_CHECK_ON;
79	(*fpu_eip)++;
80	ss = base >> 6;
81	index = (base >> 3) & 7;
82	base &= 7;
83
84	if ((mod == 0) && (base == 5))
85		offset = 0;	/* No base register */
86	else
87		offset = REG_(base);
88
89	if (index == 4) {
90		/* No index register */
91		/* A non-zero ss is illegal */
92		if (ss)
93			EXCEPTION(EX_Invalid);
94	} else {
95		offset += (REG_(index)) << ss;
96	}
97
98	if (mod == 1) {
99		/* 8 bit signed displacement */
100		long displacement;
101		RE_ENTRANT_CHECK_OFF;
102		FPU_code_access_ok(1);
103		FPU_get_user(displacement, (signed char __user *)(*fpu_eip));
104		offset += displacement;
105		RE_ENTRANT_CHECK_ON;
106		(*fpu_eip)++;
107	} else if (mod == 2 || base == 5) {	/* The second condition also has mod==0 */
108		/* 32 bit displacement */
109		long displacement;
110		RE_ENTRANT_CHECK_OFF;
111		FPU_code_access_ok(4);
112		FPU_get_user(displacement, (long __user *)(*fpu_eip));
113		offset += displacement;
114		RE_ENTRANT_CHECK_ON;
115		(*fpu_eip) += 4;
116	}
117
118	return offset;
119}
120
121static unsigned long vm86_segment(u_char segment, struct address *addr)
122{
123	segment--;
124#ifdef PARANOID
125	if (segment > PREFIX_SS_) {
126		EXCEPTION(EX_INTERNAL | 0x130);
127		math_abort(FPU_info, SIGSEGV);
128	}
129#endif /* PARANOID */
130	addr->selector = VM86_REG_(segment);
131	return (unsigned long)VM86_REG_(segment) << 4;
132}
133
134/* This should work for 16 and 32 bit protected mode. */
135static long pm_address(u_char FPU_modrm, u_char segment,
136		       struct address *addr, long offset)
137{
138	struct desc_struct descriptor;
139	unsigned long base_address, limit, address, seg_top;
140
141	segment--;
142
143#ifdef PARANOID
144	/* segment is unsigned, so this also detects if segment was 0: */
145	if (segment > PREFIX_SS_) {
146		EXCEPTION(EX_INTERNAL | 0x132);
147		math_abort(FPU_info, SIGSEGV);
148	}
149#endif /* PARANOID */
150
151	switch (segment) {
152	case PREFIX_GS_ - 1:
153		/* user gs handling can be lazy, use special accessors */
154		addr->selector = get_user_gs(FPU_info->regs);
155		break;
156	default:
157		addr->selector = PM_REG_(segment);
158	}
159
160	descriptor = FPU_get_ldt_descriptor(addr->selector);
161	base_address = SEG_BASE_ADDR(descriptor);
162	address = base_address + offset;
163	limit = base_address
164	    + (SEG_LIMIT(descriptor) + 1) * SEG_GRANULARITY(descriptor) - 1;
165	if (limit < base_address)
166		limit = 0xffffffff;
167
168	if (SEG_EXPAND_DOWN(descriptor)) {
169		if (SEG_G_BIT(descriptor))
170			seg_top = 0xffffffff;
171		else {
172			seg_top = base_address + (1 << 20);
173			if (seg_top < base_address)
174				seg_top = 0xffffffff;
175		}
176		access_limit =
177		    (address <= limit) || (address >= seg_top) ? 0 :
178		    ((seg_top - address) >= 255 ? 255 : seg_top - address);
179	} else {
180		access_limit =
181		    (address > limit) || (address < base_address) ? 0 :
182		    ((limit - address) >= 254 ? 255 : limit - address + 1);
183	}
184	if (SEG_EXECUTE_ONLY(descriptor) ||
185	    (!SEG_WRITE_PERM(descriptor) && (FPU_modrm & FPU_WRITE_BIT))) {
186		access_limit = 0;
187	}
188	return address;
189}
190
191/*
192       MOD R/M byte:  MOD == 3 has a special use for the FPU
193                      SIB byte used iff R/M = 100b
194
195       7   6   5   4   3   2   1   0
196       .....   .........   .........
197        MOD    OPCODE(2)     R/M
198
199       SIB byte
200
201       7   6   5   4   3   2   1   0
202       .....   .........   .........
203        SS      INDEX        BASE
204
205*/
206
207void __user *FPU_get_address(u_char FPU_modrm, unsigned long *fpu_eip,
208			     struct address *addr, fpu_addr_modes addr_modes)
209{
210	u_char mod;
211	unsigned rm = FPU_modrm & 7;
212	long *cpu_reg_ptr;
213	int address = 0;	/* Initialized just to stop compiler warnings. */
214
215	/* Memory accessed via the cs selector is write protected
216	   in `non-segmented' 32 bit protected mode. */
217	if (!addr_modes.default_mode && (FPU_modrm & FPU_WRITE_BIT)
218	    && (addr_modes.override.segment == PREFIX_CS_)) {
219		math_abort(FPU_info, SIGSEGV);
220	}
221
222	addr->selector = FPU_DS;	/* Default, for 32 bit non-segmented mode. */
223
224	mod = (FPU_modrm >> 6) & 3;
225
226	if (rm == 4 && mod != 3) {
227		address = sib(mod, fpu_eip);
228	} else {
229		cpu_reg_ptr = &REG_(rm);
230		switch (mod) {
231		case 0:
232			if (rm == 5) {
233				/* Special case: disp32 */
234				RE_ENTRANT_CHECK_OFF;
235				FPU_code_access_ok(4);
236				FPU_get_user(address,
237					     (unsigned long __user
238					      *)(*fpu_eip));
239				(*fpu_eip) += 4;
240				RE_ENTRANT_CHECK_ON;
241				addr->offset = address;
242				return (void __user *)address;
243			} else {
244				address = *cpu_reg_ptr;	/* Just return the contents
245							   of the cpu register */
246				addr->offset = address;
247				return (void __user *)address;
248			}
249		case 1:
250			/* 8 bit signed displacement */
251			RE_ENTRANT_CHECK_OFF;
252			FPU_code_access_ok(1);
253			FPU_get_user(address, (signed char __user *)(*fpu_eip));
254			RE_ENTRANT_CHECK_ON;
255			(*fpu_eip)++;
256			break;
257		case 2:
258			/* 32 bit displacement */
259			RE_ENTRANT_CHECK_OFF;
260			FPU_code_access_ok(4);
261			FPU_get_user(address, (long __user *)(*fpu_eip));
262			(*fpu_eip) += 4;
263			RE_ENTRANT_CHECK_ON;
264			break;
265		case 3:
266			/* Not legal for the FPU */
267			EXCEPTION(EX_Invalid);
268		}
269		address += *cpu_reg_ptr;
270	}
271
272	addr->offset = address;
273
274	switch (addr_modes.default_mode) {
275	case 0:
276		break;
277	case VM86:
278		address += vm86_segment(addr_modes.override.segment, addr);
279		break;
280	case PM16:
281	case SEG32:
282		address = pm_address(FPU_modrm, addr_modes.override.segment,
283				     addr, address);
284		break;
285	default:
286		EXCEPTION(EX_INTERNAL | 0x133);
287	}
288
289	return (void __user *)address;
290}
291
292void __user *FPU_get_address_16(u_char FPU_modrm, unsigned long *fpu_eip,
293				struct address *addr, fpu_addr_modes addr_modes)
294{
295	u_char mod;
296	unsigned rm = FPU_modrm & 7;
297	int address = 0;	/* Default used for mod == 0 */
298
299	/* Memory accessed via the cs selector is write protected
300	   in `non-segmented' 32 bit protected mode. */
301	if (!addr_modes.default_mode && (FPU_modrm & FPU_WRITE_BIT)
302	    && (addr_modes.override.segment == PREFIX_CS_)) {
303		math_abort(FPU_info, SIGSEGV);
304	}
305
306	addr->selector = FPU_DS;	/* Default, for 32 bit non-segmented mode. */
307
308	mod = (FPU_modrm >> 6) & 3;
309
310	switch (mod) {
311	case 0:
312		if (rm == 6) {
313			/* Special case: disp16 */
314			RE_ENTRANT_CHECK_OFF;
315			FPU_code_access_ok(2);
316			FPU_get_user(address,
317				     (unsigned short __user *)(*fpu_eip));
318			(*fpu_eip) += 2;
319			RE_ENTRANT_CHECK_ON;
320			goto add_segment;
321		}
322		break;
323	case 1:
324		/* 8 bit signed displacement */
325		RE_ENTRANT_CHECK_OFF;
326		FPU_code_access_ok(1);
327		FPU_get_user(address, (signed char __user *)(*fpu_eip));
328		RE_ENTRANT_CHECK_ON;
329		(*fpu_eip)++;
330		break;
331	case 2:
332		/* 16 bit displacement */
333		RE_ENTRANT_CHECK_OFF;
334		FPU_code_access_ok(2);
335		FPU_get_user(address, (unsigned short __user *)(*fpu_eip));
336		(*fpu_eip) += 2;
337		RE_ENTRANT_CHECK_ON;
338		break;
339	case 3:
340		/* Not legal for the FPU */
341		EXCEPTION(EX_Invalid);
342		break;
343	}
344	switch (rm) {
345	case 0:
346		address += FPU_info->regs->bx + FPU_info->regs->si;
347		break;
348	case 1:
349		address += FPU_info->regs->bx + FPU_info->regs->di;
350		break;
351	case 2:
352		address += FPU_info->regs->bp + FPU_info->regs->si;
353		if (addr_modes.override.segment == PREFIX_DEFAULT)
354			addr_modes.override.segment = PREFIX_SS_;
355		break;
356	case 3:
357		address += FPU_info->regs->bp + FPU_info->regs->di;
358		if (addr_modes.override.segment == PREFIX_DEFAULT)
359			addr_modes.override.segment = PREFIX_SS_;
360		break;
361	case 4:
362		address += FPU_info->regs->si;
363		break;
364	case 5:
365		address += FPU_info->regs->di;
366		break;
367	case 6:
368		address += FPU_info->regs->bp;
369		if (addr_modes.override.segment == PREFIX_DEFAULT)
370			addr_modes.override.segment = PREFIX_SS_;
371		break;
372	case 7:
373		address += FPU_info->regs->bx;
374		break;
375	}
376
377      add_segment:
378	address &= 0xffff;
379
380	addr->offset = address;
381
382	switch (addr_modes.default_mode) {
383	case 0:
384		break;
385	case VM86:
386		address += vm86_segment(addr_modes.override.segment, addr);
387		break;
388	case PM16:
389	case SEG32:
390		address = pm_address(FPU_modrm, addr_modes.override.segment,
391				     addr, address);
392		break;
393	default:
394		EXCEPTION(EX_INTERNAL | 0x131);
395	}
396
397	return (void __user *)address;
398}
399