From: bde@runx.oz.au (Bruce Evans)
Subject: Re: [386bsd]  How To Nuke 386bsd!!!
Organization: RUNX Un*x Timeshare.  Sydney, Australia.
Date: Tue, 15 Dec 92 21:07:10 GMT

In article <1992Dec7.034604.14841@netcom.com> hasty@netcom.com (Amancio Hasty Jr) writes:
>[...]
>The problem maybe related to the way that you motherboard handles
>floating point exception. Try a simple program that does a floating
>point divide by 0.0. If the system re-boots you got a bad motherboard.

There are so many bugs in the kernel and libraries involving floating
point exceptions that it's hard to tell if the motherboard is bad.  I
seem to have a "bad" 486 motherboard (386BSD-0.1 panics) but I have
rewritten the exception handler so that there are no problems.

Here is a test program that does a lot of floating point divides by 0.0
and panics 386BSD-0.1 on a bad (any?) motherboard fairly quickly.  Compile
it with -DMAX_FAILURES=16 to limit the output.  Comment out any test that
always fails to emphasize the other tests.

I am interested in how non-386BSD 386-486 systems handle this test.  It
requires gcc to compile so I haven't been able to run it on any, but
previous tests suggest that failure of all the "no-wait" tests except
the one for fnclex is typical, and when the fnclex test doesn't fail all
the time, it fails intermittently.

---
#include <signal.h>
#include <stdio.h>

#define TEST(x, y) ( ++tests, setup(), ({ asm(x); 0; }), check(x, y) )
#define CW_ZM	(1 << 2)	/* divide by zero mask */
#define SW_BUSY	(1 << 15)	/* FPU busy */
#define SW_ES	(1 << 7)	/* exception summary */
#define SW_ZE	(1 << 2)	/* divide by zero (pending) exception seen */

static double double_in_mem;
static unsigned short fp_cw;
static unsigned fp_env[7];
static unsigned fp_state[7 + 8 * 10 / sizeof(unsigned)];
static unsigned short fp_sw;
static unsigned failures;
static unsigned long tests;
static volatile /* sig_atomic_t */ int sigfpe_handled;
static volatile /* sig_atomic_t */ int sigint_handled;

static void delay(void);
static int check(char *insn, int sigfpe_expected);
static void setup(void);
static void sigfpe_handler(int sig_num);
static void sigint_handler(int sig_num);

static int check(char *insn, int sigfpe_expected)
{
    if (sigfpe_handled)
    {
	signal(SIGFPE, sigfpe_handler);
	sigfpe_handled = 0;
	if (sigfpe_expected)
	    return 1;
    }
    else if (!sigfpe_expected)
	return 1;
    ++failures;
    fprintf(stderr, "T %lu F %u: %s SIGFPE for `%s'\n",
	    tests, failures, sigfpe_expected ? "no" : "  ", insn);
    return 0;
}

static void delay(void)
{
    volatile unsigned countdown;

    for (countdown = 100; countdown != 0; --countdown)
	;
}

int main(argc, argv)
int argc;
char **argv;
{
    signal(SIGINT, sigint_handler);
#ifdef MAX_FAILURES
    while (failures < MAX_FAILURES && !sigint_handled)
#else
    while (!sigint_handled)
#endif
    {
	/*
	 * Cause a divide by zero error.  This should not trigger an exception.
	 * The next no-wait FP instruction should trigger the exception.
	 */
	if (TEST("fldz; fld1; fdiv %st,%st(1)", 0))
	{
	    /*
	     * The wait instruction should always trigger a pending exception.
	     *
	     * One way for this to fail is if the kernel uses CR0_EM instead
	     * of CR0_TS | CR0_MP to handle FP context switching.  This fails
	     * to trap fwaits immeditatly after an FP context switch.  It is
	     * especially bad when FP is being emulated.  Then all fwaits are
	     * ignored!
	     */
	    TEST("fldz; fld1; fdiv %st,%st(1); call _delay; fwait", 1);

	    /*
	     * No-wait instructions should never trigger a pending exception.
	     *
	     * On my 486 system, they are all broken when the IRQ13 FP
	     * exception reporting method is used.  On at least one 386 system,
	     * fnclex usually works but some of the others are broken, and
	     * fnclex fails after a context switch, presumably because frstor
	     * fails in the kernel.
	     */
	    TEST("fldz; fld1; fdiv %st,%st(1); fninit", 0);
	    TEST("fldz; fld1; fdiv %st,%st(1); fnstcw _fp_cw", 0);
	    TEST("fldz; fld1; fdiv %st,%st(1); fnstsw _fp_sw", 0);
	    TEST("fldz; fld1; fdiv %st,%st(1); fnclex", 0);
	    TEST("fldz; fld1; fdiv %st,%st(1); fnstenv _fp_env", 0);
	    TEST("fldz; fld1; fdiv %st,%st(1); fnsave _fp_state", 0);
	}

	/*
	 * fldenv and frstor of an error state should not trigger an exception,
	 * and they should not lose the pending exception.  Fake the pending
	 * exception so that these tests can be done even if the tests for
	 * fnstenv and fnsave of the pending exception failed.
	 */
	setup();
	asm("fnstenv _fp_env");	/* an almost clean env */
	fp_env[1] |= SW_BUSY | SW_ES | SW_ZE;	/* fake excepttion */
	++tests;
	asm("fldenv _fp_env");
	if (check("fldenv of pending exception", 0))
	{
	    delay();
	    asm("fwait");
	    ++tests;
	    check("fwait after fldenv of pending exception", 1);
	}
	setup();
	asm("fnsave _fp_state");	/* an almost clean state */
	fp_state[1] |= SW_BUSY | SW_ES | SW_ZE;	/* fake excepttion */
	++tests;
	asm("frstor _fp_state");
	if (check("frstor of pending exception", 0))
	{
	    delay();
	    asm("fwait");
	    ++tests;
	    check("fwait after frstor of pending exception", 1);
	}

	/*
	 * fstpl to memory when the FP stack is empty sometimes causes an
	 * IRQ13 a little after the intstruction.  When the fstpl is traced,
	 * the exception appears to come from the trace trap handler!  frstor
	 * of a pending error may also cause an IRQ13 after the instruction.
	 */
	TEST("fstpl _double_in_mem", 0);
    }
    fprintf(stderr, "%lu tests, %u failures\n", tests, failures);
    return failures ? 1 : 0;
}

static void setup(void)
{
    asm("fwait; fninit; fnstcw _fp_cw");
    fp_cw &= ~CW_ZM;
    asm("fldcw _fp_cw");
    signal(SIGFPE, sigfpe_handler);
    sigfpe_handled = 0;
}

static void sigfpe_handler(int sig_num)
{
    sigfpe_handled = 1;
}

static void sigint_handler(int sig_num)
{
    sigint_handled = 1;
}
---
-- 
Bruce Evans  (bde@runx.oz.au)