Fwd: 5-STABLE kernel build with icc broken
Matthew Dillon
dillon at apollo.backplane.com
Fri Apr 1 10:18:54 PST 2005
Here is the core of the FPU setup and restoration code for the kernel
bcopy in DragonFly, from i386/bcopy.s.
DragonFly uses the TD_SAVEFPU-is-a-pointer method that was outlined in
the original comment in the FreeBSD code. I further enhance the
algorithm to guarentee that the FPU is in a sane state (does not
require any further initialization other then a clts) if userland has
NOT used it. However, there are definitely some race cases that
must be considered (see the comments).
The on-fault handling in DragonFly is stackable (which further simplifies
the whole mess of on-fault vs non-on-fault copying code) and the DFly
bcopy just sets up the frame for it whether or not the onfault handling
is actually needed.
This could be further optimized, but I had already spent at least a month
on it and had to move on to other things. In particular, the setting
of CR0_TS and the restoration of TD_SAVEFPU could be moved to the
syscall-return code, so multiple in-kernel bcopy operations could be
issued without any further FPU setup or teardown.
-Matt
/*
* RACES/ALGORITHM:
*
* If gd_npxthread is not NULL we must save the application's
* current FP state to the current save area and then NULL
* out gd_npxthread to interlock against new interruptions
* changing the FP state further.
*
* If gd_npxthread is NULL the FP unit is in a known 'safe'
* state and may be used once the new save area is installed.
*
* race(1): If an interrupt occurs just prior to calling fxsave
* all that happens is that fxsave gets a npxdna trap, restores
* the app's environment, and immediately traps, restores,
* and saves it again.
*
* race(2): No interrupt can safely occur after we NULL-out
* npxthread until we fninit, because the kernel assumes that
* the FP unit is in a safe state when npxthread is NULL. It's
* more convenient to use a cli sequence here (it is not
* considered to be in the critical path), but a critical
* section would also work.
*
* race(3): The FP unit is in a known state (because npxthread
* was either previously NULL or we saved and init'd and made
* it NULL). This is true even if we are preempted and the
* preempting thread uses the FP unit, because it will be
* fninit's again on return. ANY STATE WE SAVE TO THE FPU MAY
* BE DESTROYED BY PREEMPTION WHILE NPXTHREAD IS NULL! However,
* an interrupt occuring inbetween clts and the setting of
* gd_npxthread may set the TS bit again and cause the next
* npxdna() to panic when it sees a non-NULL gd_npxthread.
*
* We can safely set TD_SAVEFPU to point to a new uninitialized
* save area and then set GD_NPXTHREAD to non-NULL. If an
* interrupt occurs after we set GD_NPXTHREAD, all that happens
* is that the safe FP state gets saved and restored. We do not
* need to fninit again.
*
* We can safely clts after setting up the new save-area, before
* installing gd_npxthread, even if we get preempted just after
* calling clts. This is because the FP unit will be in a safe
* state while gd_npxthread is NULL. Setting gd_npxthread will
* simply lock-in that safe-state. Calling clts saves
* unnecessary trap overhead since we are about to use the FP
* unit anyway and don't need to 'restore' any state prior to
* that first use.
*/
#define MMX_SAVE_BLOCK(missfunc) \
cmpl $2048,%ecx ; \
jb missfunc ; \
movl MYCPU,%eax ; /* EAX = MYCPU */ \
btsl $1,GD_FPU_LOCK(%eax) ; \
jc missfunc ; \
pushl %ebx ; \
pushl %ecx ; \
movl GD_CURTHREAD(%eax),%edx ; /* EDX = CURTHREAD */ \
movl TD_SAVEFPU(%edx),%ebx ; /* save app save area */\
addl $TDPRI_CRIT,TD_PRI(%edx) ; \
cmpl $0,GD_NPXTHREAD(%eax) ; \
je 100f ; \
fxsave 0(%ebx) ; /* race(1) */ \
movl $0,GD_NPXTHREAD(%eax) ; /* interlock intr */ \
clts ; \
fninit ; /* race(2) */ \
100: ; \
leal GD_SAVEFPU(%eax),%ecx ; \
movl %ecx,TD_SAVEFPU(%edx) ; \
clts ; \
movl %edx,GD_NPXTHREAD(%eax) ; /* race(3) */ \
subl $TDPRI_CRIT,TD_PRI(%edx) ; /* crit_exit() */ \
cmpl $0,GD_REQFLAGS(%eax) ; \
je 101f ; \
cmpl $TDPRI_CRIT,TD_PRI(%edx) ; \
jge 101f ; \
call lwkt_yield_quick ; \
/* note: eax,ecx,edx destroyed */ \
101: ; \
movl (%esp),%ecx ; \
movl $mmx_onfault,(%esp) ;
/*
* When restoring the application's FP state we must first clear
* npxthread to prevent further saves, then restore the pointer
* to the app's save area. We do not have to (and should not)
* restore the app's FP state now. Note that we do not have to
* call fninit because our use of the FP guarentees that it is in
* a 'safe' state (at least for kernel use).
*
* NOTE: it is not usually safe to mess with CR0 outside of a
* critical section, because TS may get set by a preemptive
* interrupt. However, we *can* race a load/set-ts/store against
* an interrupt doing the same thing.
*/
#define MMX_RESTORE_BLOCK \
addl $4,%esp ; \
MMX_RESTORE_BLOCK2
#define MMX_RESTORE_BLOCK2 \
movl MYCPU,%ecx ; \
movl GD_CURTHREAD(%ecx),%edx ; \
movl $0,GD_NPXTHREAD(%ecx) ; \
movl %ebx,TD_SAVEFPU(%edx) ; \
smsw %ax ; \
popl %ebx ; \
orb $CR0_TS,%al ; \
lmsw %ax ; \
movl $0,GD_FPU_LOCK(%ecx)
More information about the freebsd-hackers
mailing list