xref: /openbmc/linux/arch/x86/math-emu/get_address.c (revision e2f1cf25)
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 
30 static 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 
43 static 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 
56 static 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 */
70 static 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 
121 static 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. */
135 static 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 
207 void __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 
292 void __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