windows-nt/Source/XPSP1/NT/base/fs/utils/pentbug/pentnt.c
2020-09-26 16:20:57 +08:00

750 lines
17 KiB
C

/*++
Copyright (c) 1994-2000 Microsoft Corporation
Module Name:
pentnt.c
Abstract:
This module contains a simple program to detect the Pentium FPU
FDIV precision error, and offers to force floating point emulation
on if the bug is present.
Author:
Bryan M. Willman (bryanwi) 7-Dec-1994
Revision History:
--*/
#define UNICODE
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
#include <windows.h>
#include <ctype.h>
#include <assert.h>
#include <locale.h>
#include <stdarg.h>
#include "pbmsg.h"
void SetForceNpxEmulation(ULONG setting);
void TestForDivideError();
void ScanArgs(int argc, char **argv);
void GetSystemState();
void printmessage (DWORD messageID, ...);
int ms_p5_test_fdiv(void);
//
// Core control state vars
//
BOOLEAN NeedHelp;
BOOLEAN Force;
ULONG ForceValue;
BOOLEAN FDivError;
BOOLEAN NTOK;
ULONG CurrentForceValue;
ULONG FloatHardware;
//
// ForceValue and CurrentForceValue
//
#define FORCE_OFF 0 // User wants emulation turned off
#define FORCE_CONDITIONAL 1 // User wants emulation iff we detect bad pentium
#define FORCE_ALWAYS 2 // User wants emulation regardless
//
// hardware fp status
//
#define FLOAT_NONE 0 // No fp hardware
#define FLOAT_ON 1 // Fp hardware is present and active
#define FLOAT_OFF 2 // Fp hardware is present and disabled
void
__cdecl
main(
int argc,
char **argv
)
/*++
Routine Description:
Main procedure for pentnt.
First, we call a series of routines that build a state vector
in some booleans.
We'll then act on these control variables:
NeedHelp - User has asked for help, or made a command error
Force - True if user has asked to change a force setting
ForceValue - Has no meaning if Force is FALSE. Else says
what the user wants us to do.
FloatHardware - Indicates if there is any and whether it's on
NTOK - Indicates if first OS version with fix is what
we are running
FdivError - if TRUE, FP gives WRONG answer, else, gives right answer
CurrentForceValue - what the current force setting is
All of these will be set before we do any work.
Arguments:
argc - count of arguments, including the name of our proggram
argv - argument list - see command line syntax above
Return Value:
Exit(0) - nothing changed, and current state is OK
Exit(1) - either a state change was requested, or just help,
or the current state may have a problem.
Exit(2) - we hit something really weird....
--*/
{
CHAR lBuf[16];
DWORD dwCodePage;
LANGID LangId;
//
// build up state vector in global booleans
//
ScanArgs(argc, argv);
GetSystemState();
TestForDivideError();
/*
printf("NeedHelp = %d Force = %d ForceValue = %d\n",
NeedHelp, Force, ForceValue);
printf("FDivError = %d NTOK = %d CurrentForceValue = %d FloatHardware = %d\n",
FDivError, NTOK, CurrentForceValue, FloatHardware);
*/
// Since FormatMessage checks the current TEB's locale, and the Locale for
// CHCP is initialized when the message class is initialized, the TEB has to
// be updated after the code page is changed successfully.
// Why are we doing this, you ask. Well, the FE guys have plans to add
// more than one set of language resources to this module, but not all
// the possible resources. So this limited set is what they plan for.
// If FormatMessage can't find the right language, it falls back to
// something hopefully useful.
// Set the console output CP to OEMCP.
SetConsoleOutputCP(GetOEMCP());
dwCodePage = GetConsoleOutputCP();
sprintf(lBuf, ".%d", dwCodePage);
switch( dwCodePage )
{
case 932:
LangId = MAKELANGID( LANG_JAPANESE, SUBLANG_DEFAULT );
break;
case 949:
LangId = MAKELANGID( LANG_KOREAN, SUBLANG_KOREAN );
break;
case 936:
LangId = MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED );
break;
case 950:
LangId = MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL );
break;
default:
LangId = PRIMARYLANGID(LANGIDFROMLCID( GetUserDefaultLCID() ));
if (LangId == LANG_JAPANESE ||
LangId == LANG_KOREAN ||
LangId == LANG_CHINESE ) {
LangId = MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US );
}
else {
LangId = MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT );
}
break;
}
SetThreadLocale( MAKELCID(LangId, SORT_DEFAULT) );
setlocale(LC_ALL, lBuf);
//
// ok, we know the state of the command and the machine, do work
//
//
// if they asked for help, or did something that indicates they don't
// understand how the program works, print help and exit.
//
if (NeedHelp) {
printmessage(MSG_PENTBUG_HELP);
exit(1);
}
//
// never do anything if there's no floating point hardware in the box
//
if (FloatHardware == FLOAT_NONE) {
printmessage(MSG_PENTBUG_NO_FLOAT_HARDWARE);
exit(0);
}
//
// never do anything if it's the wrong version of NT.
//
if (!NTOK) {
printmessage(MSG_PENTBUG_NEED_NTOK);
exit(1);
}
assert(NTOK == TRUE);
assert(NeedHelp == FALSE);
assert((FloatHardware == FLOAT_ON) || (FloatHardware == FLOAT_OFF));
if (Force) {
switch (ForceValue) {
case FORCE_OFF:
if (CurrentForceValue == FORCE_OFF) {
if (FloatHardware == FLOAT_ON) {
//
// user wants fp on, fp is on, fp set to be on
// all is as it should be
//
printmessage(MSG_PENTBUG_IS_OFF_OK);
exit(FDivError);
}
if (FloatHardware == FLOAT_OFF) {
//
// user need to reboot to finish turning emulation off
//
printmessage(MSG_PENTBUG_IS_OFF_REBOOT);
exit(1);
}
} else {
//
// they want it off, it's not off, so turn it off
// remind them to reboot
//
SetForceNpxEmulation(FORCE_OFF);
printmessage(MSG_PENTBUG_TURNED_OFF);
printmessage(MSG_PENTBUG_REBOOT);
exit(1);
}
break;
case FORCE_CONDITIONAL:
if (CurrentForceValue == FORCE_CONDITIONAL) {
if (FDivError) {
//
// tell them to reboot
//
printmessage(MSG_PENTBUG_IS_ON_COND_REBOOT);
exit(1);
} else {
//
// tell them to be happy
//
printmessage(MSG_PENTBUG_IS_ON_COND_OK);
exit(0);
}
} else {
//
// set it to what they want and tell them to reboot
//
SetForceNpxEmulation(ForceValue);
printmessage(MSG_PENTBUG_TURNED_ON_CONDITIONAL);
printmessage(MSG_PENTBUG_REBOOT);
exit(1);
}
break;
case FORCE_ALWAYS:
if (CurrentForceValue == FORCE_ALWAYS) {
if (FloatHardware == FLOAT_OFF) {
//
// tell them to be happy
//
printmessage(MSG_PENTBUG_IS_ON_ALWAYS_OK);
exit(0);
} else {
//
// tell them to reboot to finish
//
printmessage(MSG_PENTBUG_IS_ON_ALWAYS_REBOOT);
exit(1);
}
} else {
SetForceNpxEmulation(ForceValue);
printmessage(MSG_PENTBUG_TURNED_ON_ALWAYS);
printmessage(MSG_PENTBUG_REBOOT);
exit(1);
}
break;
default:
printf("pentnt: INTERNAL ERROR\n");
exit(2);
} // switch
}
//
// no action requested, just report state and give advice
//
assert(Force == FALSE);
if (!FDivError) {
if (FloatHardware == FLOAT_ON) {
printmessage(MSG_PENTBUG_FLOAT_WORKS);
} else {
printmessage(MSG_PENTBUG_EMULATION_ON_AND_WORKS);
}
exit(0);
}
//
// since we're here, we have an fdiv error, tell user what to do about it
//
assert(FDivError);
printmessage(MSG_PENTBUG_FDIV_ERROR);
if ((CurrentForceValue == FORCE_CONDITIONAL) ||
(CurrentForceValue == FORCE_ALWAYS))
{
printmessage(MSG_PENTBUG_IS_ON_REBOOT);
exit(1);
}
printmessage(MSG_PENTBUG_CRITICAL_WORK);
exit(1);
assert((TRUE==FALSE));
}
VOID
SetForceNpxEmulation(
ULONG Setting
)
/*++
Routine Description:
SetForceNpxEmulation will simply set the ForceNpxEmulation value
entry under the Session Manager key to the value passed in.
0 = off
1 = conditional
2 = always
If the set attempt fails, exit with a message.
--*/
{
HKEY hkey;
LONG rc;
rc = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("System\\CurrentControlSet\\Control\\Session Manager"),
0,
KEY_WRITE,
&hkey
);
if (rc != ERROR_SUCCESS) {
printmessage(MSG_PENTBUG_SET_FAILED, rc);
exit(2);
}
rc = RegSetValueEx(
hkey,
TEXT("ForceNpxEmulation"),
0,
REG_DWORD,
(unsigned char *)&Setting,
sizeof(ULONG)
);
if (rc != ERROR_SUCCESS) {
printmessage(MSG_PENTBUG_SET_FAILED, rc);
exit(2);
}
return;
}
VOID
ScanArgs(
int argc,
char **argv
)
/*++
Routine Description:
ScanArgs - parse command line arguments, and set control flags
to reflect what we find.
Sets NeedHelp, Force, ForceValue.
Arguments:
argc - count of command line args
argv - argument vector
Return Value:
--*/
{
int i;
Force = FALSE;
NeedHelp = FALSE;
for (i = 1; i < argc; i++) {
if ( ! ((argv[i][0] == '-') ||
(argv[i][0] == '/')) )
{
NeedHelp = TRUE;
goto done;
}
switch (argv[i][1]) {
case '?':
case 'h':
case 'H':
NeedHelp = TRUE;
break;
case 'c':
case 'C':
if (Force) {
NeedHelp = TRUE;
} else {
Force = TRUE;
ForceValue = FORCE_CONDITIONAL;
}
break;
case 'f':
case 'F':
if (Force) {
NeedHelp = TRUE;
} else {
Force = TRUE;
ForceValue = FORCE_ALWAYS;
}
break;
case 'o':
case 'O':
if (Force) {
NeedHelp = TRUE;
} else {
Force = TRUE;
ForceValue = FORCE_OFF;
}
break;
default:
NeedHelp = TRUE;
goto done;
}
}
done:
if (NeedHelp) {
Force = FALSE;
}
return;
}
VOID
GetSystemState(
)
/*++
Routine Description:
GetSystemState - get the system version, whether the computer
has FP hardware or not, and whether the force
emulation switch is already set or not.
Sets FloatHardware, NTOK, CurrentForceValue
Arguments:
Return Value:
--*/
{
HKEY hkey;
TCHAR Buffer[32];
DWORD BufferSize = 32;
DWORD Type;
int major;
int minor;
LONG rc;
PULONG p;
OSVERSIONINFO OsVersionInfo;
NTOK = FALSE;
//
// decide if the system version is OK.
//
OsVersionInfo.dwOSVersionInfoSize = sizeof(OsVersionInfo);
GetVersionEx(&OsVersionInfo);
if (OsVersionInfo.dwPlatformId != VER_PLATFORM_WIN32_NT) {
printmessage(MSG_PENTBUG_NOT_NT);
exit(2);
}
if ( (OsVersionInfo.dwMajorVersion > 3) ||
( (OsVersionInfo.dwMajorVersion == 3) &&
(OsVersionInfo.dwMinorVersion >= 51) ))
{
//
// build 3.51 or greater, it has the fix
//
NTOK = TRUE;
} else if ( (OsVersionInfo.dwMajorVersion == 3) &&
(OsVersionInfo.dwMinorVersion == 50))
{
if (OsVersionInfo.szCSDVersion[0] != (TCHAR)'\0') {
//
// we have a service pack for 3.5, since pack 1 and
// later have the fix, it's OK
//
NTOK = TRUE;
}
}
/*
printf("debug NTOK forced true for testing\n\n\n");
NTOK = TRUE;
*/
//
// determine if float hardware is present
//
rc = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("Hardware\\Description\\System\\FloatingPointProcessor"),
0,
KEY_READ,
&hkey
);
if (rc == ERROR_SUCCESS) {
FloatHardware = FLOAT_ON;
RegCloseKey(hkey);
} else {
rc = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("Hardware\\Description\\System\\DisabledFloatingPointProcessor"),
0,
KEY_READ,
&hkey
);
if (rc == ERROR_SUCCESS) {
FloatHardware = FLOAT_OFF;
RegCloseKey(hkey);
} else {
FloatHardware = FLOAT_NONE;
}
}
//
// determine if emulation has been forced on
//
rc = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("System\\CurrentControlSet\\Control\\Session Manager"),
0,
KEY_READ,
&hkey
);
if (rc != ERROR_SUCCESS) {
return;
}
BufferSize = 32;
rc = RegQueryValueEx(
hkey,
TEXT("ForceNpxEmulation"),
0,
&Type,
(unsigned char *)Buffer,
&BufferSize
);
if ( (rc == ERROR_SUCCESS) &&
(Type == REG_DWORD) )
{
p = (PULONG)Buffer;
CurrentForceValue = *p;
}
return;
}
//
// these must be globals to make the compiler do the right thing
//
VOID
TestForDivideError(
)
/*++
Routine Description:
Do a divide with a known divident/divisor pair, followed by
a multiply to see if we get the right answer back.
FDivError = TRUE if we get the WRONG answer, FALSE.
Arguments:
Return Value:
--*/
{
DWORD pick;
HANDLE ph;
DWORD processmask;
DWORD systemmask;
ULONG i;
//
// fetch the affinity mask, which is also effectively a list
// of processors
//
ph = GetCurrentProcess();
GetProcessAffinityMask(ph, &processmask, &systemmask);
//
// step through the mask, testing each cpu.
// if any is bad, we treat them all as bad
//
FDivError = FALSE;
for (i = 0; i < 32; i++) {
pick = 1 << i;
if ((systemmask & pick) != 0) {
//*//printf("pick = %08lx\n", pick);
SetThreadAffinityMask(GetCurrentThread(), pick);
//
// call the official test function
//
if (ms_p5_test_fdiv()) {
//
// do NOT just assign func to FDivError, that will reset
// it if a second cpu is good. must be one way flag
//
FDivError = TRUE;
}
} // IF
} // for
return;
}
/***
* testfdiv.c - routine to test for correct operation of x86 FDIV instruction.
*
*Purpose:
* Detects early steppings of Pentium with incorrect FDIV tables using
* 'official' Intel test values. Returns 1 if flawed Pentium is detected,
* 0 otherwise.
*
*/
int ms_p5_test_fdiv(void)
{
double dTestDivisor = 3145727.0;
double dTestDividend = 4195835.0;
double dRslt;
_asm {
fld qword ptr [dTestDividend]
fdiv qword ptr [dTestDivisor]
fmul qword ptr [dTestDivisor]
fsubr qword ptr [dTestDividend]
fstp qword ptr [dRslt]
}
return (dRslt > 1.0);
}
//
// Call FormatMessage and dump the result. All messages to Stdout
//
void printmessage (DWORD messageID, ...)
{
unsigned short messagebuffer[4096];
va_list ap;
va_start(ap, messageID);
FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, NULL, messageID, 0,
messagebuffer, 4095, &ap);
wprintf(messagebuffer);
va_end(ap);
}