]> git.saurik.com Git - apple/xnu.git/blame - osfmk/i386/fpu.c
xnu-344.2.tar.gz
[apple/xnu.git] / osfmk / i386 / fpu.c
CommitLineData
1c79356b
A
1/*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22/*
23 * @OSF_COPYRIGHT@
24 */
25/*
26 * Mach Operating System
27 * Copyright (c) 1992-1990 Carnegie Mellon University
28 * All Rights Reserved.
29 *
30 * Permission to use, copy, modify and distribute this software and its
31 * documentation is hereby granted, provided that both the copyright
32 * notice and this permission notice appear in all copies of the
33 * software, derivative works or modified versions, and any portions
34 * thereof, and that both notices appear in supporting documentation.
35 *
36 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
37 * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
38 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
39 *
40 * Carnegie Mellon requests users of this software to return to
41 *
42 * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
43 * School of Computer Science
44 * Carnegie Mellon University
45 * Pittsburgh PA 15213-3890
46 *
47 * any improvements or extensions that they make and grant Carnegie Mellon
48 * the rights to redistribute these changes.
49 */
50/*
51 */
52
53#include <cpus.h>
54#include <platforms.h>
55
56#include <mach/exception_types.h>
57#include <mach/i386/thread_status.h>
58#include <mach/i386/fp_reg.h>
59
60#include <kern/mach_param.h>
61#include <kern/thread.h>
62#include <kern/zalloc.h>
63#include <kern/misc_protos.h>
64#include <kern/spl.h>
65#include <kern/assert.h>
66
67#include <i386/thread.h>
68#include <i386/fpu.h>
69#include <i386/trap.h>
70#include <i386/pio.h>
71#include <i386/misc_protos.h>
72
73#if 0
74#include <i386/ipl.h>
75extern int curr_ipl;
76#define ASSERT_IPL(L) \
77{ \
78 if (curr_ipl != L) { \
79 printf("IPL is %d, expected %d\n", curr_ipl, L); \
80 panic("fpu: wrong ipl"); \
81 } \
82}
83#else
84#define ASSERT_IPL(L)
85#endif
86
87int fp_kind = FP_387; /* 80387 present */
88zone_t ifps_zone; /* zone for FPU save area */
89
90#if NCPUS == 1
91volatile thread_act_t fp_act = THR_ACT_NULL;
92 /* thread whose state is in FPU */
93 /* always THR_ACT_NULL if emulating FPU */
94volatile thread_act_t fp_intr_act = THR_ACT_NULL;
95
96
97#define clear_fpu() \
98 { \
99 set_ts(); \
100 fp_act = THR_ACT_NULL; \
101 }
102
103#else /* NCPUS > 1 */
104#define clear_fpu() \
105 { \
106 set_ts(); \
107 }
108
109#endif
110
111/* Forward */
112
113extern void fpinit(void);
114extern void fp_save(
115 thread_act_t thr_act);
116extern void fp_load(
117 thread_act_t thr_act);
118
119/*
120 * Look for FPU and initialize it.
121 * Called on each CPU.
122 */
123void
124init_fpu(void)
125{
126 unsigned short status, control;
127
128 /*
129 * Check for FPU by initializing it,
130 * then trying to read the correct bit patterns from
131 * the control and status registers.
132 */
133 set_cr0(get_cr0() & ~(CR0_EM|CR0_TS)); /* allow use of FPU */
134
135 fninit();
136 status = fnstsw();
137 fnstcw(&control);
138
139 if ((status & 0xff) == 0 &&
140 (control & 0x103f) == 0x3f)
141 {
142#if 0
143 /*
144 * We have a FPU of some sort.
145 * Compare -infinity against +infinity
146 * to check whether we have a 287 or a 387.
147 */
148 volatile double fp_infinity, fp_one, fp_zero;
149 fp_one = 1.0;
150 fp_zero = 0.0;
151 fp_infinity = fp_one / fp_zero;
152 if (fp_infinity == -fp_infinity) {
153 /*
154 * We have an 80287.
155 */
156 fp_kind = FP_287;
157 __asm__ volatile(".byte 0xdb; .byte 0xe4"); /* fnsetpm */
158 }
159 else
160#endif
161 {
162 /*
163 * We have a 387.
164 */
165 fp_kind = FP_387;
166 }
167 /*
168 * Trap wait instructions. Turn off FPU for now.
169 */
170 set_cr0(get_cr0() | CR0_TS | CR0_MP);
171 }
172 else
173 {
174 /*
175 * NO FPU.
176 */
177 fp_kind = FP_NO;
178 set_cr0(get_cr0() | CR0_EM);
179 }
180}
181
182/*
183 * Initialize FP handling.
184 */
185void
186fpu_module_init(void)
187{
188 ifps_zone = zinit(sizeof(struct i386_fpsave_state),
189 THREAD_MAX * sizeof(struct i386_fpsave_state),
190 THREAD_CHUNK * sizeof(struct i386_fpsave_state),
191 "i386 fpsave state");
192}
193
194/*
195 * Free a FPU save area.
196 * Called only when thread terminating - no locking necessary.
197 */
198void
199fp_free(fps)
200 struct i386_fpsave_state *fps;
201{
202ASSERT_IPL(SPL0);
203#if NCPUS == 1
204 if ((fp_act != THR_ACT_NULL) && (fp_act->mact.pcb->ims.ifps == fps)) {
205 /*
206 * Make sure we don't get FPU interrupts later for
207 * this thread
208 */
209 fwait();
210
211 /* Mark it free and disable access */
212 clear_fpu();
213 }
214#endif /* NCPUS == 1 */
215 zfree(ifps_zone, (vm_offset_t) fps);
216}
217
218/*
219 * Set the floating-point state for a thread.
220 * If the thread is not the current thread, it is
221 * not running (held). Locking needed against
222 * concurrent fpu_set_state or fpu_get_state.
223 */
224kern_return_t
225fpu_set_state(
226 thread_act_t thr_act,
227 struct i386_float_state *state)
228{
229 register pcb_t pcb;
230 register struct i386_fpsave_state *ifps;
231 register struct i386_fpsave_state *new_ifps;
232
233ASSERT_IPL(SPL0);
234 if (fp_kind == FP_NO)
235 return KERN_FAILURE;
236
237 assert(thr_act != THR_ACT_NULL);
238 pcb = thr_act->mact.pcb;
239
240#if NCPUS == 1
241
242 /*
243 * If this thread`s state is in the FPU,
244 * discard it; we are replacing the entire
245 * FPU state.
246 */
247 if (fp_act == thr_act) {
248 fwait(); /* wait for possible interrupt */
249 clear_fpu(); /* no state in FPU */
250 }
251#endif
252
253 if (state->initialized == 0) {
254 /*
255 * new FPU state is 'invalid'.
256 * Deallocate the fp state if it exists.
257 */
258 simple_lock(&pcb->lock);
259 ifps = pcb->ims.ifps;
260 pcb->ims.ifps = 0;
261 simple_unlock(&pcb->lock);
262
263 if (ifps != 0) {
264 zfree(ifps_zone, (vm_offset_t) ifps);
265 }
266 }
267 else {
268 /*
269 * Valid state. Allocate the fp state if there is none.
270 */
271 register struct i386_fp_save *user_fp_state;
272 register struct i386_fp_regs *user_fp_regs;
273
274 user_fp_state = (struct i386_fp_save *) &state->hw_state[0];
275 user_fp_regs = (struct i386_fp_regs *)
276 &state->hw_state[sizeof(struct i386_fp_save)];
277
278 new_ifps = 0;
279 Retry:
280 simple_lock(&pcb->lock);
281 ifps = pcb->ims.ifps;
282 if (ifps == 0) {
283 if (new_ifps == 0) {
284 simple_unlock(&pcb->lock);
285 new_ifps = (struct i386_fpsave_state *) zalloc(ifps_zone);
286 goto Retry;
287 }
288 ifps = new_ifps;
289 new_ifps = 0;
290 pcb->ims.ifps = ifps;
291 }
292
293 /*
294 * Ensure that reserved parts of the environment are 0.
295 */
296 bzero((char *)&ifps->fp_save_state, sizeof(struct i386_fp_save));
297
298 ifps->fp_save_state.fp_control = user_fp_state->fp_control;
299 ifps->fp_save_state.fp_status = user_fp_state->fp_status;
300 ifps->fp_save_state.fp_tag = user_fp_state->fp_tag;
301 ifps->fp_save_state.fp_eip = user_fp_state->fp_eip;
302 ifps->fp_save_state.fp_cs = user_fp_state->fp_cs;
303 ifps->fp_save_state.fp_opcode = user_fp_state->fp_opcode;
304 ifps->fp_save_state.fp_dp = user_fp_state->fp_dp;
305 ifps->fp_save_state.fp_ds = user_fp_state->fp_ds;
306 ifps->fp_regs = *user_fp_regs;
307
308 simple_unlock(&pcb->lock);
309 if (new_ifps != 0)
310 zfree(ifps_zone, (vm_offset_t) ifps);
311 }
312
313 return KERN_SUCCESS;
314}
315
316/*
317 * Get the floating-point state for a thread.
318 * If the thread is not the current thread, it is
319 * not running (held). Locking needed against
320 * concurrent fpu_set_state or fpu_get_state.
321 */
322kern_return_t
323fpu_get_state(
324 thread_act_t thr_act,
325 register struct i386_float_state *state)
326{
327 register pcb_t pcb;
328 register struct i386_fpsave_state *ifps;
329
330ASSERT_IPL(SPL0);
331 if (fp_kind == FP_NO)
332 return KERN_FAILURE;
333
334 assert(thr_act != THR_ACT_NULL);
335 pcb = thr_act->mact.pcb;
336
337 simple_lock(&pcb->lock);
338 ifps = pcb->ims.ifps;
339 if (ifps == 0) {
340 /*
341 * No valid floating-point state.
342 */
343 simple_unlock(&pcb->lock);
344 bzero((char *)state, sizeof(struct i386_float_state));
345 return KERN_SUCCESS;
346 }
347
348 /* Make sure we`ve got the latest fp state info */
349 /* If the live fpu state belongs to our target */
350#if NCPUS == 1
351 if (thr_act == fp_act)
352#else
353 if (thr_act == current_act())
354#endif
355 {
356 clear_ts();
357 fp_save(thr_act);
358 clear_fpu();
359 }
360
361 state->fpkind = fp_kind;
362 state->exc_status = 0;
363
364 {
365 register struct i386_fp_save *user_fp_state;
366 register struct i386_fp_regs *user_fp_regs;
367
368 state->initialized = ifps->fp_valid;
369
370 user_fp_state = (struct i386_fp_save *) &state->hw_state[0];
371 user_fp_regs = (struct i386_fp_regs *)
372 &state->hw_state[sizeof(struct i386_fp_save)];
373
374 /*
375 * Ensure that reserved parts of the environment are 0.
376 */
377 bzero((char *)user_fp_state, sizeof(struct i386_fp_save));
378
379 user_fp_state->fp_control = ifps->fp_save_state.fp_control;
380 user_fp_state->fp_status = ifps->fp_save_state.fp_status;
381 user_fp_state->fp_tag = ifps->fp_save_state.fp_tag;
382 user_fp_state->fp_eip = ifps->fp_save_state.fp_eip;
383 user_fp_state->fp_cs = ifps->fp_save_state.fp_cs;
384 user_fp_state->fp_opcode = ifps->fp_save_state.fp_opcode;
385 user_fp_state->fp_dp = ifps->fp_save_state.fp_dp;
386 user_fp_state->fp_ds = ifps->fp_save_state.fp_ds;
387 *user_fp_regs = ifps->fp_regs;
388 }
389 simple_unlock(&pcb->lock);
390
391 return KERN_SUCCESS;
392}
393
394/*
395 * Initialize FPU.
396 *
397 * Raise exceptions for:
398 * invalid operation
399 * divide by zero
400 * overflow
401 *
402 * Use 53-bit precision.
403 */
404void
405fpinit(void)
406{
407 unsigned short control;
408
409ASSERT_IPL(SPL0);
410 clear_ts();
411 fninit();
412 fnstcw(&control);
413 control &= ~(FPC_PC|FPC_RC); /* Clear precision & rounding control */
414 control |= (FPC_PC_53 | /* Set precision */
415 FPC_RC_RN | /* round-to-nearest */
416 FPC_ZE | /* Suppress zero-divide */
417 FPC_OE | /* and overflow */
418 FPC_UE | /* underflow */
419 FPC_IE | /* Allow NaNQs and +-INF */
420 FPC_DE | /* Allow denorms as operands */
421 FPC_PE); /* No trap for precision loss */
422 fldcw(control);
423}
424
425/*
426 * Coprocessor not present.
427 */
428
429void
430fpnoextflt(void)
431{
432 /*
433 * Enable FPU use.
434 */
435ASSERT_IPL(SPL0);
436 clear_ts();
437#if NCPUS == 1
438
439 /*
440 * If this thread`s state is in the FPU, we are done.
441 */
442 if (fp_act == current_act())
443 return;
444
445 /* Make sure we don't do fpsave() in fp_intr while doing fpsave()
446 * here if the current fpu instruction generates an error.
447 */
448 fwait();
449 /*
450 * If another thread`s state is in the FPU, save it.
451 */
452 if (fp_act != THR_ACT_NULL) {
453 fp_save(fp_act);
454 }
455
456 /*
457 * Give this thread the FPU.
458 */
459 fp_act = current_act();
460
461#endif /* NCPUS == 1 */
462
463 /*
464 * Load this thread`s state into the FPU.
465 */
466 fp_load(current_act());
467}
468
469/*
470 * FPU overran end of segment.
471 * Re-initialize FPU. Floating point state is not valid.
472 */
473
474void
475fpextovrflt(void)
476{
477 register thread_act_t thr_act = current_act();
478 register pcb_t pcb;
479 register struct i386_fpsave_state *ifps;
480
481#if NCPUS == 1
482
483 /*
484 * Is exception for the currently running thread?
485 */
486 if (fp_act != thr_act) {
487 /* Uh oh... */
488 panic("fpextovrflt");
489 }
490#endif
491
492 /*
493 * This is a non-recoverable error.
494 * Invalidate the thread`s FPU state.
495 */
496 pcb = thr_act->mact.pcb;
497 simple_lock(&pcb->lock);
498 ifps = pcb->ims.ifps;
499 pcb->ims.ifps = 0;
500 simple_unlock(&pcb->lock);
501
502 /*
503 * Re-initialize the FPU.
504 */
505 clear_ts();
506 fninit();
507
508 /*
509 * And disable access.
510 */
511 clear_fpu();
512
513 if (ifps)
514 zfree(ifps_zone, (vm_offset_t) ifps);
515
516 /*
517 * Raise exception.
518 */
519 i386_exception(EXC_BAD_ACCESS, VM_PROT_READ|VM_PROT_EXECUTE, 0);
520 /*NOTREACHED*/
521}
522
523/*
524 * FPU error. Called by AST.
525 */
526
527void
528fpexterrflt(void)
529{
530 register thread_act_t thr_act = current_act();
531
532ASSERT_IPL(SPL0);
533#if NCPUS == 1
534 /*
535 * Since FPU errors only occur on ESC or WAIT instructions,
536 * the current thread should own the FPU. If it didn`t,
537 * we should have gotten the task-switched interrupt first.
538 */
539 if (fp_act != THR_ACT_NULL) {
540 panic("fpexterrflt");
541 return;
542 }
543
544 /*
545 * Check if we got a context switch between the interrupt and the AST
546 * This can happen if the interrupt arrived after the FPU AST was
547 * checked. In this case, raise the exception in fp_load when this
548 * thread next time uses the FPU. Remember exception condition in
549 * fp_valid (extended boolean 2).
550 */
551 if (fp_intr_act != thr_act) {
552 if (fp_intr_act == THR_ACT_NULL) {
553 panic("fpexterrflt: fp_intr_act == THR_ACT_NULL");
554 return;
555 }
556 fp_intr_act->mact.pcb->ims.ifps->fp_valid = 2;
557 fp_intr_act = THR_ACT_NULL;
558 return;
559 }
560 fp_intr_act = THR_ACT_NULL;
561#else /* NCPUS == 1 */
562 /*
563 * Save the FPU state and turn off the FPU.
564 */
565 fp_save(thr_act);
566#endif /* NCPUS == 1 */
567
568 /*
569 * Raise FPU exception.
570 * Locking not needed on pcb->ims.ifps,
571 * since thread is running.
572 */
573 i386_exception(EXC_ARITHMETIC,
574 EXC_I386_EXTERR,
575 thr_act->mact.pcb->ims.ifps->fp_save_state.fp_status);
576 /*NOTREACHED*/
577}
578
579/*
580 * Save FPU state.
581 *
582 * Locking not needed:
583 * . if called from fpu_get_state, pcb already locked.
584 * . if called from fpnoextflt or fp_intr, we are single-cpu
585 * . otherwise, thread is running.
586 */
587
588void
589fp_save(
590 thread_act_t thr_act)
591{
592 register pcb_t pcb = thr_act->mact.pcb;
593 register struct i386_fpsave_state *ifps = pcb->ims.ifps;
594
595 if (ifps != 0 && !ifps->fp_valid) {
596 /* registers are in FPU */
597 ifps->fp_valid = TRUE;
598 fnsave(&ifps->fp_save_state);
599 }
600}
601
602/*
603 * Restore FPU state from PCB.
604 *
605 * Locking not needed; always called on the current thread.
606 */
607
608void
609fp_load(
610 thread_act_t thr_act)
611{
612 register pcb_t pcb = thr_act->mact.pcb;
613 register struct i386_fpsave_state *ifps;
614
615ASSERT_IPL(SPL0);
616 ifps = pcb->ims.ifps;
617 if (ifps == 0) {
618 ifps = (struct i386_fpsave_state *) zalloc(ifps_zone);
619 bzero((char *)ifps, sizeof *ifps);
620 pcb->ims.ifps = ifps;
621 fpinit();
622#if 1
623/*
624 * I'm not sure this is needed. Does the fpu regenerate the interrupt in
625 * frstor or not? Without this code we may miss some exceptions, with it
626 * we might send too many exceptions.
627 */
628 } else if (ifps->fp_valid == 2) {
629 /* delayed exception pending */
630
631 ifps->fp_valid = TRUE;
632 clear_fpu();
633 /*
634 * Raise FPU exception.
635 * Locking not needed on pcb->ims.ifps,
636 * since thread is running.
637 */
638 i386_exception(EXC_ARITHMETIC,
639 EXC_I386_EXTERR,
640 thr_act->mact.pcb->ims.ifps->fp_save_state.fp_status);
641 /*NOTREACHED*/
642#endif
643 } else {
644 frstor(ifps->fp_save_state);
645 }
646 ifps->fp_valid = FALSE; /* in FPU */
647}
648
649/*
650 * Allocate and initialize FP state for current thread.
651 * Don't load state.
652 *
653 * Locking not needed; always called on the current thread.
654 */
655void
656fp_state_alloc(void)
657{
658 pcb_t pcb = current_act()->mact.pcb;
659 struct i386_fpsave_state *ifps;
660
661 ifps = (struct i386_fpsave_state *)zalloc(ifps_zone);
662 bzero((char *)ifps, sizeof *ifps);
663 pcb->ims.ifps = ifps;
664
665 ifps->fp_valid = TRUE;
666 ifps->fp_save_state.fp_control = (0x037f
667 & ~(FPC_IM|FPC_ZM|FPC_OM|FPC_PC))
668 | (FPC_PC_53|FPC_IC_AFF);
669 ifps->fp_save_state.fp_status = 0;
670 ifps->fp_save_state.fp_tag = 0xffff; /* all empty */
671}
672
673
674/*
675 * fpflush(thread_act_t)
676 * Flush the current act's state, if needed
677 * (used by thread_terminate_self to ensure fp faults
678 * aren't satisfied by overly general trap code in the
679 * context of the reaper thread)
680 */
681void
682fpflush(thread_act_t thr_act)
683{
684#if NCPUS == 1
685 if (fp_act && thr_act == fp_act) {
686 clear_ts();
687 fwait();
688 clear_fpu();
689 }
690#else
691 /* not needed on MP x86s; fp not lazily evaluated */
692#endif
693}
694
695
696/*
697 * Handle a coprocessor error interrupt on the AT386.
698 * This comes in on line 5 of the slave PIC at SPL1.
699 */
700
701void
702fpintr(void)
703{
704 spl_t s;
705 thread_act_t thr_act = current_act();
706
707ASSERT_IPL(SPL1);
708 /*
709 * Turn off the extended 'busy' line.
710 */
711 outb(0xf0, 0);
712
713 /*
714 * Save the FPU context to the thread using it.
715 */
716#if NCPUS == 1
717 if (fp_act == THR_ACT_NULL) {
718 printf("fpintr: FPU not belonging to anyone!\n");
719 clear_ts();
720 fninit();
721 clear_fpu();
722 return;
723 }
724
725 if (fp_act != thr_act) {
726 /*
727 * FPU exception is for a different thread.
728 * When that thread again uses the FPU an exception will be
729 * raised in fp_load. Remember the condition in fp_valid (== 2).
730 */
731 clear_ts();
732 fp_save(fp_act);
733 fp_act->mact.pcb->ims.ifps->fp_valid = 2;
734 fninit();
735 clear_fpu();
736 /* leave fp_intr_act THR_ACT_NULL */
737 return;
738 }
739 if (fp_intr_act != THR_ACT_NULL)
740 panic("fp_intr: already caught intr");
741 fp_intr_act = thr_act;
742#endif /* NCPUS == 1 */
743
744 clear_ts();
745 fp_save(thr_act);
746 fninit();
747 clear_fpu();
748
749 /*
750 * Since we are running on the interrupt stack, we must
751 * signal the thread to take the exception when we return
752 * to user mode. Use an AST to do this.
753 *
754 * Don`t set the thread`s AST field. If the thread is
755 * descheduled before it takes the AST, it will notice
756 * the FPU error when it reloads its FPU state.
757 */
758 s = splsched();
759 mp_disable_preemption();
760 ast_on(AST_I386_FP);
761 mp_enable_preemption();
762 splx(s);
763}