Floating point in interrupt handler

Daan Vreeken [PA4DAN] Danovitsch at vitsch.net
Tue Oct 23 05:00:53 PDT 2007


On Tuesday 23 October 2007 07:52:41 Issei Suzuki wrote:
> 2007/10/23, Daan Vreeken [PA4DAN] <Danovitsch at vitsch.net>:
> > So I've added asm inline functions to use the FPU's fsin and fcos
> > operands. At the start of my loop function I (try to) save the FPU state
> > with the following code :
> >     td = curthread;
> >     npxgetregs(td, &fpu_state);
> > At the end of the function I restore the FPU state with :
> >     npxsetregs(td, &fpu_state);
> >
> > In between I currently have :
> >     // create a 500Hz sine wave, modulate it with a 2Hz sine wave and
> >     // let it have a 10.0 Volt amplitude
> >     t += 0.0001;
> >     set_dac_voltage(card, 0,
> >         fp_sin(500 * 2 * M_PI * t) * fp_sin(2 * 2 * M_PI * t) * 10.0);
> >
> > As uggly as the code may seem, it works and the output of the acquisition
> > board shows the expected signal on a scope. While the code seems to do
> > what it should, the kernel floods the console with the following message
> > : kernel trap 22 with interrupts disabled
>
> In FreeBSD, FPU context switch is delayed until FPU is used for the
> first time after user thread is switched. To achieve this, T_DNA
> (FPU device not available trap) is used as follows.
>
> (Before switching thread)
> 1. Save FPU state and enable DNA trap (npxsave() @ /sys/i386/isa/npx.c).
>    After this, DNA trap occurs when you access FPU.
> (Switch to another user thread)
> 2. User thread accesses FPU.
> 3. T_DNA trap occurs, and npxdna() @ /sys/i386/isa/npx.c is called.
> 4. Initialize FPU state (1st time) or restore FPU state (2nd times or
> later). 5. Return to user mode, and user thread access FPU again.
>
> So, to use FPU in kernel, you must clear T_DNA trap and initialize FPU
> registers first.
>
> Reading these functions may help you, I think.
>
>   /sys/i386/isa/npx.c
>    start_emulating()
>    stop_emulating()
>    npxdna()

Thanks for the insights, this has helped a lot. If I understand the code 
correctly, there are 2 options when the kernel arrives at hardclock() :
o The current process is using the FPU (fpcurthread != NULL)
o The current process hasn't used the FPU (yet) since it has been switched to
   (fpcurthread == NULL)
In the first case, FPU instructions can be used and will not result in a trap, 
but we should save/restore the FPU state before using them so userland 
doesn't get confused. In the last case FPU instructions result in a trap, so 
we need stop/start_emulating(), but as no one is using the FPU, there is no 
need to save/restore it's state.

With this in mind I've come up with the following code :

At the start of the function :
	// check FPU state on entry
	if (PCPU_GET(fpcurthread) != NULL) {
		// someone is using the FPU
		// save it's state and remember to put it back later
		restore = 1;
		fpusave(&fpu_state);
	} else {
		// no one is using the FPU
		// enable use of FPU instructions, no need to save it's state
		restore = 0;
		stop_emulating();
	}
	// init FPU state every time we get here, as we don't know who has
	// been playing with it in between calls
	fninit();
	control = __INITIAL_NPXCW__;
	fldcw(&control);

Then we do some floating point arithmetic.

And at the end of the function :
	// restore FPU state before we leave
	if (restore) {
		// restore FPU registers to what they were
		fpurstor(&fpu_state);
	} else {
		// no one was using the FPU, so re-enable the FPU trap
		start_emulating();
	}

With this code trap-22 has stopped to trigger within my function. The FPU 
instructions still seem to be executed correctly in my function and when 
adding a couple of printf()'s I can see it fpusave() and fpurstor() when 
interrupting a userland process that uses the FPU.
Does this look reasonable to everyone?


Thanks,
-- 
Daan


More information about the freebsd-hackers mailing list