/*++ 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 #include #include #include #include #include #include #include #include #include #include #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); }