--- Net2/arch/i386/isa/npx.c 2018/04/24 18:07:06 1.1.1.3 +++ Net2/arch/i386/isa/npx.c 2018/04/24 18:20:03 1.1.1.5 @@ -31,9 +31,9 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * @(#)npx.c 7.2 (Berkeley) 5/12/91 + * from: @(#)npx.c 7.2 (Berkeley) 5/12/91 + * npx.c,v 1.7 1993/06/14 07:06:49 cgd Exp */ -static char rcsid[] = "$Header: /var/lib/cvsd/net2/Net2/arch/i386/isa/npx.c,v 1.1.1.3 2018/04/24 18:07:06 root Exp $"; #include "npx.h" #if NNPX > 0 @@ -46,171 +46,507 @@ static char rcsid[] = "$Header: /var/lib #include "machine/pcb.h" #include "machine/trap.h" #include "ioctl.h" +#include "i386/isa/icu.h" #include "machine/specialreg.h" #include "i386/isa/isa_device.h" -#include "icu.h" +#include "i386/isa/isa.h" + /* * 387 and 287 Numeric Coprocessor Extension (NPX) Driver. */ -int npxprobe(), npxattach(), npxintr(); +#ifdef __GNUC__ + +#define disable_intr() __asm("cli") +#define enable_intr() __asm("sti") +#define fldcw(addr) __asm("fldcw %0" : : "m" (*addr)) +#define fnclex() __asm("fnclex") +#define fninit() __asm("fninit") +#define fnsave(addr) __asm("fnsave %0" : "=m" (*addr) : "0" (*addr)) +#define fnstcw(addr) __asm("fnstcw %0" : "=m" (*addr) : "0" (*addr)) +#define fnstsw(addr) __asm("fnstsw %0" : "=m" (*addr) : "0" (*addr)) +#define fp_divide_by_0() __asm("fldz; fld1; fdiv %st,%st(1); fwait") +#define frstor(addr) __asm("frstor %0" : : "m" (*addr)) +#define fwait() __asm("fwait") +#define read_eflags() ({u_long ef; \ + __asm("pushf; popl %0" : "=a" (ef)); \ + ef; }) +#define start_emulating() __asm("smsw %%ax; orb %0,%%al; lmsw %%ax" \ + : : "n" (CR0_TS) : "ax") +#define stop_emulating() __asm("clts") +#define write_eflags(ef) __asm("pushl %0; popf" : : "a" ((u_long) ef)) + +#else /* not __GNUC__ */ + +void disable_intr __P((void)); +void enable_intr __P((void)); +void fldcw __P((caddr_t addr)); +void fnclex __P((void)); +void fninit __P((void)); +void fnsave __P((caddr_t addr)); +void fnstcw __P((caddr_t addr)); +void fnstsw __P((caddr_t addr)); +void fp_divide_by_0 __P((void)); +void frstor __P((caddr_t addr)); +void fwait __P((void)); +u_long read_eflags __P((void)); +void start_emulating __P((void)); +void stop_emulating __P((void)); +void write_eflags __P((u_long ef)); + +#endif /* __GNUC__ */ + +typedef u_char bool_t; + +extern struct gate_descriptor idt[]; + +int npxdna __P((void)); +void npxexit __P((struct proc *p)); +void npxinit __P((u_int control)); +void npxintr __P((struct intrframe frame)); +void npxsave __P((struct save87 *addr)); +static int npxattach __P((struct isa_device *dvp)); +static int npxprobe __P((struct isa_device *dvp)); +static int npxprobe1 __P((struct isa_device *dvp)); + struct isa_driver npxdriver = { npxprobe, npxattach, "npx", }; -struct proc *npxproc; /* process who owns device, otherwise zero */ -struct pcb *npxpcb; /* owners context structure */ -int npxexists; -extern long npx0mask; +u_int npx0mask; +struct proc *npxproc; + +static bool_t npx_ex16; +static bool_t npx_exists; +static struct gate_descriptor npx_idt_probeintr; +static int npx_intrno; +static volatile u_int npx_intrs_while_probing; +static bool_t npx_irq13; +static volatile u_int npx_traps_while_probing; + +/* + * Special interrupt handlers. Someday intr0-intr15 will be used to count + * interrupts. We'll still need a special exception 16 handler. The busy + * latch stuff in probintr() can be moved to npxprobe(). + */ +void probeintr(void); +asm ( \ + ".text;" \ + "_probeintr:;" \ + "ss;" \ + "incl _npx_intrs_while_probing;" \ + "pushl %eax;" \ + "movb $0x20,%al;" /* EOI (asm in strings loses cpp features) */ \ + "outb %al,$0xa0;" /* IO_ICU2 */ \ + "outb %al,$0x20;" /* IO_ICU1 */ \ + "movb $0,%al;" \ + "outb %al,$0xf0;" /* clear BUSY# latch */ \ + "popl %eax;" \ + "iret" \ +); + +void probetrap(void); +asm +(" + .text +_probetrap: + ss + incl _npx_traps_while_probing + fnclex + iret +"); /* - * Probe routine - look device, otherwise set emulator bit + * Probe routine. Initialize cr0 to give correct behaviour for [f]wait + * whether the device exists or not (XXX should be elsewhere). Set flags + * to tell npxattach() what to do. Modify device struct if npx doesn't + * need to use interrupts. Return 1 if device exists. */ +static int npxprobe(dvp) struct isa_device *dvp; -{ static status, control; +{ + int result; + u_long save_eflags; + u_char save_icu1_mask; + u_char save_icu2_mask; + struct gate_descriptor save_idt_npxintr; + struct gate_descriptor save_idt_npxtrap; + /* + * This routine is now just a wrapper for npxprobe1(), to install + * special npx interrupt and trap handlers, to enable npx interrupts + * and to disable other interrupts. Someday isa_configure() will + * install suitable handlers and run with interrupts enabled so we + * won't need to do so much here. + */ + npx_intrno = NRSVIDT + ffs(dvp->id_irq) - 1; + save_eflags = read_eflags(); + disable_intr(); + save_icu1_mask = inb(IO_ICU1 + 1); + save_icu2_mask = inb(IO_ICU2 + 1); + save_idt_npxintr = idt[npx_intrno]; + save_idt_npxtrap = idt[16]; + outb(IO_ICU1 + 1, ~(IRQ_SLAVE | dvp->id_irq)); + outb(IO_ICU2 + 1, ~(dvp->id_irq >> 8)); + setidt(16, probetrap, SDT_SYS386TGT, SEL_KPL); + setidt(npx_intrno, probeintr, SDT_SYS386IGT, SEL_KPL); + npx_idt_probeintr = idt[npx_intrno]; + enable_intr(); + result = npxprobe1(dvp); + disable_intr(); + outb(IO_ICU1 + 1, save_icu1_mask); + outb(IO_ICU2 + 1, save_icu2_mask); + idt[npx_intrno] = save_idt_npxintr; + idt[16] = save_idt_npxtrap; + write_eflags(save_eflags); + return (result); +} +static int +npxprobe1(dvp) + struct isa_device *dvp; +{ + int control; + int status; #ifdef lint npxintr(); #endif - - /* insure EM bit off */ - load_cr0(rcr0() & ~CR0_EM); /* stop emulating */ - asm(" fninit "); /* put device in known state */ - - /* check for a proper status of zero */ - status = 0x5a5a; - asm (" fnstsw %0 " : "=m" (status) : "m" (status) ); - - if ((status&0xff) == 0) { - - /* good, now check for a proper control word */ - asm (" fnstcw %0 " : "=m" (status) : "m" (status)); - - if ((status&0x103f) == 0x3f) { - /* then we have a numeric coprocessor */ - /* XXX should force an exception here to generate an intr */ - return (1); + /* + * Partially reset the coprocessor, if any. Some BIOS's don't reset + * it after a warm boot. + */ + outb(0xf1, 0); /* full reset on some systems, NOP on others */ + outb(0xf0, 0); /* clear BUSY# latch */ + /* + * Prepare to trap all ESC (i.e., NPX) instructions and all WAIT + * instructions. We must set the CR0_MP bit and use the CR0_TS + * bit to control the trap, because setting the CR0_EM bit does + * not cause WAIT instructions to trap. It's important to trap + * WAIT instructions - otherwise the "wait" variants of no-wait + * control instructions would degenerate to the "no-wait" variants + * after FP context switches but work correctly otherwise. It's + * particularly important to trap WAITs when there is no NPX - + * otherwise the "wait" variants would always degenerate. + * + * Try setting CR0_NE to get correct error reporting on 486DX's. + * Setting it should fail or do nothing on lesser processors. + */ + load_cr0(rcr0() | CR0_MP | CR0_NE); + /* + * But don't trap while we're probing. + */ + stop_emulating(); + /* + * Finish resetting the coprocessor, if any. If there is an error + * pending, then we may get a bogus IRQ13, but probeintr() will handle + * it OK. Bogus halts have never been observed, but we enabled + * IRQ13 and cleared the BUSY# latch early to handle them anyway. + */ + fninit(); + DELAY(1000); /* wait for any IRQ13 (fwait might hang) */ +#ifdef DIAGNOSTIC + if (npx_intrs_while_probing != 0) + printf("fninit caused %u bogus npx interrupt(s)\n", + npx_intrs_while_probing); + if (npx_traps_while_probing != 0) + printf("fninit caused %u bogus npx trap(s)\n", + npx_traps_while_probing); +#endif + /* + * Check for a status of mostly zero. + */ + status = 0x5a5a; + fnstsw(&status); + if ((status & 0xb8ff) == 0) { + /* + * Good, now check for a proper control word. + */ + control = 0x5a5a; + fnstcw(&control); + if ((control & 0x1f3f) == 0x033f) { + npx_exists = 1; + /* + * We have an npx, now divide by 0 to see if exception + * 16 works. + */ + control &= ~(1 << 2); /* enable divide by 0 trap */ + fldcw(&control); + npx_traps_while_probing = npx_intrs_while_probing = 0; + fp_divide_by_0(); + if (npx_traps_while_probing != 0) { + /* + * Good, exception 16 works. + */ + npx_ex16 = 1; + dvp->id_irq = 0; /* zap the interrupt */ + return 16; + } + if (npx_intrs_while_probing != 0) { + /* + * Bad, we are stuck with IRQ13. + */ + npx_irq13 = 1; + npx0mask = dvp->id_irq; /* npxattach too late */ + return 16; + } + /* + * Worse, even IRQ13 is broken. Use emulator. + */ } } - - /* insure EM bit on */ - load_cr0(rcr0() | CR0_EM); /* start emulating */ - return (0); + /* + * Probe failed, but we want to get to npxattach to initialize the + * emulator and say that it has been installed. XXX handle devices + * that aren't really devices better. + */ + dvp->id_irq = 0; + return 16; } /* * Attach routine - announce which it is, and wire into system */ +int npxattach(dvp) struct isa_device *dvp; { - + if (npx_ex16) + printf("npx%d: using exception 16\n", dvp->id_unit); + else if (npx_irq13) + ; + else { +#ifdef MATH_EMULATE + if (npx_exists) + printf("npx%d: error reporting broken, using emulator\n", + dvp->id_unit); + else + printf("npx%d: emulator\n", dvp->id_unit); +#else + panic("npxattach: no math emulator in kernel!"); +#endif + } npxinit(__INITIAL_NPXCW__); - npxexists++; - npx0mask = dvp->id_irq; + return (1); } /* * Initialize floating point unit. */ -npxinit(control) { - static short wd; - - if (npxexists == 0) return; - - - wd = control; - wd = 0x272; - load_cr0(rcr0() & ~CR0_EM); /* stop emulating */ - asm (" fninit"); - asm(" fldcw %0" : : "g" (wd)); - if (curpcb) { - asm(" fnsave %0 " : : "g" (curpcb->pcb_savefpu) ); - curpcb->pcb_flags |= FP_NEEDSRESTORE; - } - load_cr0(rcr0() | CR0_EM); /* start emulating */ - outb(0xb1,0); /* reset processor */ -} - -/* - * Load floating point context and record ownership to suite - */ -npxload() { +void +npxinit(control) + u_int control; +{ + struct save87 dummy; - if (npxproc) panic ("npxload"); - npxproc = curproc; - npxpcb = curpcb; - asm(" frstor %0 " : : "g" (curpcb->pcb_savefpu) ); + if (!npx_exists) + return; + /* + * fninit has the same h/w bugs as fnsave. Use the detoxified + * fnsave to throw away any junk in the fpu. fnsave initializes + * the fpu and sets npxproc = NULL as important side effects. + */ + npxsave(&dummy); + stop_emulating(); + fldcw(&control); + if (curpcb != NULL) + fnsave(&curpcb->pcb_savefpu); + start_emulating(); } /* - * Unload floating point context and relinquish ownership + * Free coprocessor (if we have it). */ -npxunload() { - - if (npxproc == 0) panic ("npxunload"); - asm(" fsave %0 " : : "g" (npxpcb->pcb_savefpu) ); - npxproc = 0 ; +void +npxexit(p) + struct proc *p; +{ + if (p == npxproc) { + start_emulating(); + npxproc = NULL; + } } /* - * Record information needed in processing an exception and clear status word + * Record the FPU state and reinitialize it all except for the control word. + * Then generate a SIGFPE. + * + * Reinitializing the state allows naive SIGFPE handlers to longjmp without + * doing any fixups. + * + * XXX there is currently no way to pass the full error state to signal + * handlers, and if this is a nested interrupt there is no way to pass even + * a status code! So there is no way to have a non-naive SIGFPE handler. At + * best a handler could do an fninit followed by an fldcw of a static value. + * fnclex would be of little use because it would leave junk on the FPU stack. + * Returning from the handler would be even less safe than usual because + * IRQ13 exception handling makes exceptions even less precise than usual. */ -npxintr(frame) struct intrframe frame; { +void +npxintr(frame) + struct intrframe frame; +{ int code; -static status; - - outb(0xf0,0); /* reset processor */ -/*pg("npxintr");*/ - - asm (" fnstsw %0 " : "=m" (status) : "m" (status) ); - /* sync state in process context structure, in advance of debugger/process looking for it */ - if (npxproc == 0 || npxexists == 0) panic ("npxintr"); - asm (" fnsave %0 " : : "g" (npxpcb->pcb_savefpu) ); + if (npxproc == NULL || !npx_exists) { + /* XXX no %p in stand/printf.c. Cast to quiet gcc -Wall. */ + printf("npxintr: npxproc = %lx, curproc = %lx, npx_exists = %d\n", + (u_long) npxproc, (u_long) curproc, npx_exists); + panic("npxintr from nowhere"); + } + if (npxproc != curproc) { + printf("npxintr: npxproc = %lx, curproc = %lx, npx_exists = %d\n", + (u_long) npxproc, (u_long) curproc, npx_exists); + panic("npxintr from non-current process"); + } + /* + * Save state. This does an implied fninit. It had better not halt + * the cpu or we'll hang. + */ + outb(0xf0, 0); + fnsave(&curpcb->pcb_savefpu); + fwait(); + /* + * Restore control word (was clobbered by fnsave). + */ + fldcw(&curpcb->pcb_savefpu.sv_env.en_cw); + fwait(); + /* + * Remember the exception status word and tag word. The current + * (almost fninit'ed) fpu state is in the fpu and the exception + * state just saved will soon be junk. However, the implied fninit + * doesn't change the error pointers or register contents, and we + * preserved the control word and will copy the status and tag + * words, so the complete exception state can be recovered. + */ + curpcb->pcb_savefpu.sv_ex_sw = curpcb->pcb_savefpu.sv_env.en_sw; + curpcb->pcb_savefpu.sv_ex_tw = curpcb->pcb_savefpu.sv_env.en_tw; + + /* + * Pass exception to process. + */ + if (ISPL(frame.if_cs) == SEL_UPL) { + /* + * Interrupt is essentially a trap, so we can afford to call + * the SIGFPE handler (if any) as soon as the interrupt + * returns. + * + * XXX little or nothing is gained from this, and plenty is + * lost - the interrupt frame has to contain the trap frame + * (this is otherwise only necessary for the rescheduling trap + * in doreti, and the frame for that could easily be set up + * just before it is used). + */ + curproc->p_regs = (int *)&frame.if_es; + curpcb->pcb_flags |= FM_TRAP; /* used by sendsig */ #ifdef notyet - /* encode the appropriate code for detailed information on this exception */ - code = ???; + /* + * Encode the appropriate code for detailed information on + * this exception. + */ + code = XXX_ENCODE(curpcb->pcb_savefpu.sv_ex_sw); #else - code = 0; /* XXX */ + code = 0; /* XXX */ #endif - -/*if((pg("status %x", status) & 0x7f) == 't') {*/ - /* pass exception to process, which may not be the current one */ - if (npxproc == curproc) { - /* Q: what if in an interrupt, or in trap processing? */ - if (ISPL(frame.if_cs) == SEL_UPL) { - curproc->p_regs = (int *)&frame.if_es; - curpcb->pcb_flags |= FM_TRAP; /* used by sendsig */ - } /* else printf("*");*/ trapsignal(curproc, SIGFPE, code); - curpcb->pcb_flags &= ~FM_TRAP; /* used by sendsig */ + curpcb->pcb_flags &= ~FM_TRAP; } else { - /* printf("P");*/ + /* + * Nested interrupt. These losers occur when: + * o an IRQ13 is bogusly generated at a bogus time, e.g.: + * o immediately after an fnsave or frstor of an + * error state. + * o a couple of 386 instructions after + * "fstpl _memvar" causes a stack overflow. + * These are especially nasty when combined with a + * trace trap. + * o an IRQ13 occurs at the same time as another higher- + * priority interrupt. + * + * Treat them like a true async interrupt. + */ psignal(npxproc, SIGFPE); } -/*}*/ - - /* clear the exception so we can catch others like it */ - asm (" fnclex"); } /* * Implement device not available (DNA) exception + * + * It would be better to switch FP context here (only). This would require + * saving the state in the proc table instead of in the pcb. */ -npxdna() { -/*pg("npxdna");*/ - - - if (npxexists == 0) return(0); - load_cr0(rcr0() & ~CR0_EM); /* stop emulating */ - if (curpcb->pcb_flags & FP_NEEDSRESTORE) - asm(" frstor %0 " : : "g" (curpcb->pcb_savefpu)); - curpcb->pcb_flags |= FP_WASUSED | FP_NEEDSSAVE; - curpcb->pcb_flags &= ~FP_NEEDSRESTORE; +int +npxdna() +{ + if (!npx_exists) + return (0); + if (npxproc != NULL) { + printf("npxdna: npxproc = %lx, curproc = %lx\n", + (u_long) npxproc, (u_long) curproc); + panic("npxdna"); + } + stop_emulating(); + /* + * Record new context early in case frstor causes an IRQ13. + */ npxproc = curproc; - npxpcb = curpcb; + /* + * The following frstor may cause an IRQ13 when the state being + * restored has a pending error. The error will appear to have been + * triggered by the current (npx) user instruction even when that + * instruction is a no-wait instruction that should not trigger an + * error (e.g., fnclex). On at least one 486 system all of the + * no-wait instructions are broken the same as frstor, so our + * treatment does not amplify the breakage. On at least one + * 386/Cyrix 387 system, fnclex works correctly while frstor and + * fnsave are broken, so our treatment breaks fnclex if it is the + * first FPU instruction after a context switch. + */ + frstor(&curpcb->pcb_savefpu); + return (1); } -#endif + +/* + * Wrapper for fnsave instruction to handle h/w bugs. If there is an error + * pending, then fnsave generates a bogus IRQ13 on some systems. Force + * any IRQ13 to be handled immediately, and then ignore it. This routine is + * often called at splhigh so it must not use many system services. In + * particular, it's much easier to install a special handler than to + * guarantee that it's safe to use npxintr() and its supporting code. + */ +void +npxsave(addr) + struct save87 *addr; +{ + u_char icu1_mask; + u_char icu2_mask; + u_char old_icu1_mask; + u_char old_icu2_mask; + struct gate_descriptor save_idt_npxintr; + + disable_intr(); + old_icu1_mask = inb(IO_ICU1 + 1); + old_icu2_mask = inb(IO_ICU2 + 1); + save_idt_npxintr = idt[npx_intrno]; + outb(IO_ICU1 + 1, old_icu1_mask & ~(IRQ_SLAVE | npx0mask)); + outb(IO_ICU2 + 1, old_icu2_mask & ~(npx0mask >> 8)); + idt[npx_intrno] = npx_idt_probeintr; + enable_intr(); + stop_emulating(); + fnsave(addr); + fwait(); + start_emulating(); + npxproc = NULL; + disable_intr(); + icu1_mask = inb(IO_ICU1 + 1); /* masks may have changed */ + icu2_mask = inb(IO_ICU2 + 1); + outb(IO_ICU1 + 1, + (icu1_mask & ~npx0mask) | (old_icu1_mask & npx0mask)); + outb(IO_ICU2 + 1, + (icu2_mask & ~(npx0mask >> 8)) + | (old_icu2_mask & (npx0mask >> 8))); + idt[npx_intrno] = save_idt_npxintr; + enable_intr(); /* back to usual state */ +} + +#endif /* NNPX > 0 */