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