Brendan wrote:Hi,
My copy of the Intel manuals say that after FNINIT the status word is set to 0 and the control word is set to 0x037F (not the other way around).
Yes, I actually confused both concepts. The previous code was doing it in the right order.
Now I made another layout following the Intel recommendations. It seems to work, but I don't have external FPU's so I couldn't really check for the differences between built-in/external or 287/387.
In any case, it seems to be OK so far (we are talking here about 386SX+ only, though), since it properly seems to check for presence, built-in status and type (it's easier to read the detection order of logic in the "main" block):
Code: Select all
bits 16
org 100h
START:
///INIT: main()
///INIT: main()
///INIT: main()
///INIT: main()
//Try to set sane 16-bit stack pointers:
///
$EBP &= 0xFFFF;
$ESP &= 0xFFFF;
//Reset variables:
///
byte[fpu_found]=0;
byte[fpu_builtin]=0;
byte[fputype]=0;
//Try to find the FPU:
///
cdecl x87_fpu_find();
if($AL==1) //FPU found?
{
byte[fpu_found]=1;
//Set CR0 with proper values for a present FPU:
///
$EAX = $CR0;
$AL &= 11111011b; //Clear EM flag
$AL |= 00000010b; //Set MP flag
$CR0 = $EAX;
//Try to see if CPUID is supported:
///
check_cpuid_support();
if($AL==1) //CPUID supported?
{
//Try to see if the FPU is built-in
//via CPUID instruction:
///
cpuid_says_fpu_builtin();
if($AL==1) //FPU built-in?
{
byte[fpu_builtin]=1;
$EAX = $CR0;
$AL |= 00100000b; //Set NE flag
$CR0 = $EAX;
}
}
//See if the FPU is 287 or 387:
///
cdecl fpu_type();
byte[fputype]=$AL;
}
else //FPU not found?
{
//If no FPU detected, prepare CR0 for emulation:
///
$EAX = $CR0;
$AL |= 00000100b; //Set EM flag
$AL &= 11111101b; //Clear MP flag
$CR0 = $EAX;
}
//Show results:
///
if(byte[fpu_found]==1) //FPU found?
{
$DX=fpustr;
$AH=9;
asm int 21h;
if(byte[fpu_builtin]==1) //FPU built-in?
{
$DX=fpubstr;
$AH=9;
asm int 21h;
}
else //FPU not built-in?
{
$DX=fpuxstr;
$AH=9;
asm int 21h;
}
if(byte[fputype]==2) //80287 FPU?
{
$DX=fpu287;
$AH=9;
asm int 21h;
}
else if(byte[fputype]==3) //80387 FPU?
{
$DX=fpu387;
$AH=9;
asm int 21h;
}
}
//End program:
///
$AH=4Ch;
asm int 21h;
fpu_found db 0
fpu_builtin db 0
fputype db 0
fpustr db "FPU found!",0x0D,0x0A,'$'
fpubstr db "FPU builtin!",0x0D,0x0A,'$'
fpuxstr db "FPU is external",0x0D,0x0A,'$'
fpu287 db "FPU is 80287",0x0D,0x0A,'$'
fpu387 db "FPU is 80387+",0x0D,0x0A,'$'
///END: main()
///END: main()
///END: main()
///END: main()
//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;
//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;
//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;
//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;
//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;//;
//Try to find the x87 FPU; the result is returned
//in AL:
//
// AL == 1 -- found
// AL == 0 -- not found
//
// All other registers are destroyed.
///
cdecl function x87_fpu_find()
{
//Declare local variables:
///
stackwideword tmpfpucw;
stackwideword tmpfpusw;
//OS specific:
//Don't allow interrupts; otherwise we would constantly
//have to reload segment registers with proper values
//everywhere on IRQs, etc., so consider it critical enough
//as to not to allow interrupts:
///
asm pushf
asm cli;
//Point DS to SS for this routine:
///
push $DS;
push $SS;
pop $DS;
//Take CR0 and try to clear
//the EM and MP flags:
///
$EAX = $CR0;
$AL &= 11111001b;
$CR0 = $EAX;
//Set our test value to a non-zero
//value:
///
stackword[tmpfpucw]=0x5A5A;
//Set other variables to default
//values:
///
stackword[tmpfpusw]=0x5A5A;
//Reset the FPU:
///
asm fninit;
//Fetch the control word after resetting
//the FPU to defaults:
///
$EBX=stackword tmpfpucw; //point to stack variable
asm fnstcw [ebx];
//Fetch the status word as well:
///
$EBX=stackword tmpfpusw; //point to stack variable
asm fnstsw [ebx];
//Apply the Intel-recommended AND mask
//to the Control Word:
///
stackword[tmpfpucw] &= 0x103F;
//If the FPU status word is 0 and if the
//ANDed Control Word is 0x3F, then everything
///is OK:
///
if(stackword[tmpfpusw]==0 && stackword[tmpfpucw]==0x3F)
{
$EAX=1; //Indicate that we found the FPU
}
else $EAX=0; //otherwise, indicate no FPU found
//Restore segment values:
///
pop $DS;
//Try to allow interrupts again, if they were enabled
//in the first place:
///
asm popf;
}
//This will return the status of
//support for CPUID in AL, by trying
//to alter/invert/flip the ID bit in CR0:
//
// AL == 1 -- supported
// AL == 0 -- not supported
///
function check_cpuid_support()
{
push $EBX;
push $ECX;
push $EDX;
// EAX == 32-bit EFLAGS
// EBX == backup of EAX
// ECX == mask for ID bit
// EDX == inverted mask for ID bit
///
asm pushfd;
pop $EAX;
$EBX=$EAX;
$ECX=00000000001000000000000000000000b;
$EDX=11111111110111111111111111111111b;
//Check the ID bit:
///
$EAX &= $ECX;
if($EAX==0) //Is ID bit set to 0?
{
$EAX=$EBX; //Get full EFLAGS again
$EAX|=$ECX; //Set the ID bit
push $EAX; //Put this value in stack
asm popfd; //Place in EFLAGS
asm pushfd; //EFLAGS to stack again
pop $EAX; //Put EFLAGS in EAX again
$EAX &= $ECX; //Check ID bit again
//If ID was modified, then there's
//support for CPUID...
///
if($EAX!=0)
{
$EAX >>= 21;
}
}
else //Is ID bit set to 1?
{
$EAX=$EBX; //Get full EFLAGS again
$EAX&=$EDX; //Clear the ID bit
push $EAX; //Put this value in stack
asm popfd; //Place in EFLAGS
asm pushfd; //EFLAGS to stack again
pop $EAX; //Put EFLAGS in EAX again
$EAX &= $ECX; //Check ID bit again
//If ID was modified, then there's
//support for EFLAGS...
///
if($EAX==0)
{
$AX++;
}
else
{
$EAX=0;
}
}
pop $EDX;
pop $ECX;
pop $EBX;
}
//Indicates in AL if the x87 FPU is built-in
//in the main CPU:
//
// AL == 1 -- built-in
// AL == 0 -- external
//
//All other registers are potentially, destroyed,
//specially EBX, ECX and EDX.
///
function cpuid_says_fpu_builtin()
{
$EAX=0; //Basic CPUID function
asm cpuid; //Execute this
//See if the maximum function for CPUID
//is at least 1; otherwise, we won't be able
//to determine if the FPU is builtin and
//consider it non-builtin.
///
if($EAX>=1)
{
$EAX=1; //Second CPUID function
asm cpuid; //Execute this
//The feature information found in bit 0 of
//EDX tells if the FPU is built-in, if it's
//set to 1:
$EDX &= 1;
if($DL==1)
{
$EAX=$EDX; //Indicate that the FPU is built-in
}
else
{
$EAX^=$EAX; //Set AL to 0 to indicate non-builtin FPU
}
}
else
{
$EAX^=$EAX; //Set AL to 0 to indicate non-builtin FPU
}
}
//Returns the value in AL
//
// AL == 3 -- 387+
// AL == 2 -- 287-
///
cdecl function fpu_type()
{
//Declare a local stack variable:
///
stackwideword fpu_status;
//Disable interrupts for more safety:
///
asm pushf
asm cli
push $EDX;
//Make DS==SS for our routine
push $DS;
push $SS;
pop $DS;
$EBX=stackword fpu_status; //point to stack variable
$EDX=2; //Default: indicate 80287- FPU
asm{
;See if the FPU can differentiate between
;-infinity and +infinity. If not, it must be 287-;
;if yes, it must be 387+
;;
;;;
fld1 ;Load number +1.0
fldz ;Load number +0.0
fdiv st1,st0 ;Divide 1/0 to get positive infinity
fld st0 ;Copy the result at the top of stack in ST(0)...
fchs ;...and change the sign of value in ST(0) for negative infinity
fcompp ;Compare +infinity with -infinity
fstsw [ebx] ;Store the status word
mov ax,[ebx] ;fpu_status contents in AX
sahf ;Put flags in AH into SF, ZF, AF, PF and CF flags of EFLAGS
;The C3 status flag of the FPU
;corresponds to the ZF flag of
;EFLAGS, so if it's a 287, C3 thinks
;that +infinity == -infinity
;and C3 (which is located at the
;corresponding location of ZF in EFLAGS
;will set ZF)
;
;Otherwise, if it's a 387 FPU, C3
;will be 0 as a result of FCOMPP indicating
;+infinity > -infinity or in short
;+infinity != -infinity, and ZF will
;be cleared to 0, which will cause
;to consider it to be a 387+
jz ._z287 ;If it's "the same", there's no differentiation so must be 287
inc edx ;Indicate 387+
._z287:
}
$EAX=$EDX; //Save the result to return
pop $DS;
pop $EDX;
asm popf ;try to re-enable interrupts (if they were)
}
It tries to detect FPU presence, then sees if CPUID is supported. If so, sees if FPU is built-in (if CPUID is not supported we'll assume the FPU is not built-in --maybe that's an error for 486 with builtin FPU but no CPUID support but I don't know how to workaround that yet--).
Then it sees how it treats infinity. If it doesn't differentiate +infinity and -infinity then it's a 287; otherwise it's a 387+.
Is there still something wrong here or left to do for FPU initialization?