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