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