revisions to cpu.c to better support cpuid instruction, added detection of SSE2/2, 3DNow, PAE Changelog: New file cpuid.h Refactored cpuid support previously for FREEBSD to work for all _i386_ platforms.
Index: cpu.c =================================================================== RCS file: /home/wine/wine/dlls/kernel/cpu.c,v retrieving revision 1.3 diff -u -3 -p -r1.3 cpu.c --- cpu.c 4 Dec 2003 01:41:53 -0000 1.3 +++ cpu.c 13 Jan 2004 02:39:51 -0000 @@ -31,7 +31,9 @@ #ifdef HAVE_MACHINE_CPU_H # include <machine/cpu.h> #endif - +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif #include <ctype.h> #include <string.h> #include <stdarg.h> @@ -40,6 +42,7 @@ #ifdef HAVE_SYS_TIME_H # include <sys/time.h> #endif +#include "cpuid.h" #define NONAMELESSUNION @@ -59,6 +62,101 @@ WINE_DEFAULT_DEBUG_CHANNEL(reg); #define ENTI 0x69746e65 /* "enti" */ #define CAMD 0x444d4163 /* "cAMD" */ +int OS_SSESupported(void) +{ +/* Stub for now + SSE requires the OS to support it + Test for it here and return the level of sse support (1 or 2) for yes, 0 for no + */ + int level=0; + #ifdef linux +/* Here for reference not currently used + * Please test before relying on this code + */ + { + char line[200]; + + FILE *f = fopen ("/proc/cpuinfo", "r"); + if (!f) + return 0; //Asume no sse support + + while (fgets(line,200,f)!=NULL) + { + if (strstr(line,"sse2") ) if(level < 2) level=2; + if (strstr(line,"sse") ) if(level <1) level=1; + } + return level; + } + +#endif /* linux*/ +/* + * How do we test for this on other Oses + */ + return 0; +} + +static inline int get_processor_count( void) +{ +/* Get the number of configured processors in a multiprocessor system + * The method for doing this varies widely +*/ + int count=0; + +#ifdef linux +/* Here for reference not currently used + * Please test before relying on this code + */ + { + char line[200]; + FILE *f = fopen ("/proc/cpuinfo", "r"); + if (!f) + return 1; //Asume we have at least one processor + + while (fgets(line,200,f)!=NULL) + { + if (strstr(line,"processor") )count++; + } + if(count>0) return count; + } + +#endif /* linux*/ + +#ifdef sun + count = sysconf(_SC_NPROCESSORS_ONLN); + if(count>0) return count; +#endif /* sun*/ + +#ifdef __NETBSD__ + { + int mib[2]; + int value[2]={0,0}; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + value[1] = sizeof(int); + if (sysctl(mib, 2, value, value+1, NULL, 0) >= 0) + return(value[0]); + } +#endif + +#ifdef __FREEBSD__ + { + int ret, len, num; + + len = sizeof(num); + ret = sysctlbyname("hw.ncpu", &num, &len, NULL, 0); + if(!ret) return num; + + } +#endif +/* + * In the event this is an os not suported by this routine then assume 1 CPU +*/ + +return 1; + +} + /* Calls cpuid with an eax of 'ax' and returns the 16 bytes in *p * We are compiled with -fPIC, so we can't clobber ebx. */ @@ -97,6 +195,131 @@ static inline int have_cpuid(void) #endif } +static cpuid_t CpuId(unsigned long inst) +{ + + static unsigned long _eax,_ebx,_ecx,_edx; + static cpuid_t cpuid; + + + _eax=inst; + _ebx=_edx=_ecx=0; + + +/* + * Note this is supposed to work on 386s + * It is not very likely wine will be in use on these systems + * Note that this method will fail if an interrupt modifies the ID bit + * of the flags register, but I expect this is unlikely to happen in practise + * If it does, the worst that can happen is we get identified as a 386 system + */ + + asm ( + "\tpushl %%ebx\n" + "\tmovl %%eax,%%ecx\n" + "\tpushfl\n" + "\tpopl %%eax\n" + "\tmovl %%eax,%%ebx\n" + "\txorl $2097152, %%eax\n" + "\tpushl %%eax\n" + "\tpopfl\n" + "\tpushfl\n" + "\tpopl %%eax\n" + "\tpushl %%ebx\n" + "\tpopfl\n" + "\tandl $2097152,%%eax\n" + "\tandl $2097152,%%ebx\n" + "\txorl %%eax,%%ebx\n" + "\tjnz have_cpuid\n" + "\tclrl %%ebx\n" + "\tjmp endit\n" + "have_cpuid:\tmovl %%ecx,%%eax\n" + "\tcpuid\n" + "endit:\n" + "\tmovl %%ebx,%0\n" + "\tpopl %%ebx\n" + : + "=r"(_ebx),"=a"(_eax),"=c"(_ecx),"=d"(_edx):"a"(inst) + + ); + + cpuid.eax=_eax; + cpuid.ebx=_ebx; + cpuid.ecx=_ecx; + cpuid.edx=_edx; + *(unsigned long *) &cpuid.IdString[0]=_ebx; + *(unsigned long *) &cpuid.IdString[8]=_ecx; + *(unsigned long *) &cpuid.IdString[4]=_edx; + + cpuid.IdString[12]=0; + + return(cpuid); +} +/************************************************ + * Interpet the Results of the CPUID Instruction +*/ + +cpuid_t CPUID_GetId() +{ + cpuid_t cpuid,cpuid1,cpuid2; + int amd; + + cpuid=CpuId(0); + if(!strcmp(cpuid.IdString,"AuthenticAMD")) + { + cpuid.vendor=amd=CPU_VENDOR_AMD; + cpuid2=CpuId(0x80000001); + } + cpuid1=CpuId(0x00000001); + + if(!cpuid.IdString[0]) { + + cpuid.nocpuid=1; + cpuid.features=0; + cpuid.ex_features=0; + cpuid.brand=0; + cpuid.apic=0; + cpuid.ex_family=0; + cpuid.ex_model=0; + cpuid.cpu_type=0; + cpuid.family=3; + cpuid.model=0; + cpuid.stepping=0; + strcpy(cpuid.IdString,"i386"); + return(cpuid); + } + + +/* Lets fill out the rest of the data structure + * Opcode 1 (in CPUID 1) holds the processor info + * + * Store the feature information. + */ + cpuid.nocpuid=0; + cpuid.features=cpuid1.edx; + cpuid.ex_features=cpuid2.edx; + cpuid.brand=cpuid1.ebx&0xff; + cpuid.apic=(cpuid1.ebx>>24) &0xff; + cpuid.ex_family=(cpuid1.eax>>20) &0xff; + cpuid.ex_model=(cpuid1.eax>>16) &0x0f; + cpuid.cpu_type=(cpuid1.eax>>12) &0x03; + cpuid.family=(cpuid1.eax>>8) &0x0f; + cpuid.model=(cpuid1.eax>>4) &0x0f; + cpuid.stepping=(cpuid1.eax) &0x0f; + + return(cpuid); +} + +int CPUID_IsFeatureAvailable(unsigned long feature){ +cpuid_t cpuid; + + cpuid=CPUID_GetId(); + if(cpuid.vendor==CPU_VENDOR_AMD) return(!!(cpuid.ex_features&feature)); + else return(!!(cpuid.features&feature)); +} + + + static BYTE PF[64] = {0,}; static ULONGLONG cpuHz = 1000000000; /* default to a 1GHz */ @@ -521,63 +744,62 @@ VOID WINAPI GetSystemInfo( } memcpy(si,&cachedsi,sizeof(*si)); -#elif defined(__FreeBSD__) - { +#elif defined (__i386__) + /* Note for posterity, this code should probably be used for netbsd too */ + int sse_lvl; + cpuid_t cpuId; unsigned int regs[4], regs2[4]; - int ret, len, num; + if (!have_cpuid()) regs[0] = 0; /* No cpuid support -- skip the rest */ else - do_cpuid(0x00000000, regs); /* get standard cpuid level and vendor name */ - if (regs[0]>=0x00000001) { /* Check for supported cpuid version */ - do_cpuid(0x00000001, regs2); /* get cpu features */ - switch ((regs2[0] >> 8)&0xf) { /* cpu family */ + cpuId= CPUID_GetId(); +/* get standard cpuid level and vendor name */ + if (cpuId.family!=0) + { + switch (cpuId.family) { /* cpu family */ case 3: cachedsi.dwProcessorType = PROCESSOR_INTEL_386; cachedsi.wProcessorLevel = 3; break; case 4: cachedsi.dwProcessorType = PROCESSOR_INTEL_486; cachedsi.wProcessorLevel = 4; + cachedsi.wProcessorRevision = cpuId.stepping; break; case 5: cachedsi.dwProcessorType = PROCESSOR_INTEL_PENTIUM; cachedsi.wProcessorLevel = 5; + cachedsi.wProcessorRevision = cpuId.stepping; break; case 6: case 15: /* PPro/2/3/4 has same info as P1 */ cachedsi.dwProcessorType = PROCESSOR_INTEL_PENTIUM; cachedsi.wProcessorLevel = 6; + cachedsi.wProcessorRevision = cpuId.stepping; break; default: - FIXME("unknown FreeBSD cpu family %d, please report! (-> setting to 386)\n", \ - (regs2[0] >> 8)&0xf); + FIXME("unknown FreeBSD cpu family %d, please report! (-> setting to 386)\n", cpuId.family); break; } - PF[PF_FLOATING_POINT_EMULATED] = !(regs2[3] & 1); - PF[PF_RDTSC_INSTRUCTION_AVAILABLE] = (regs2[3] & (1 << 4 )) >> 4; - PF[PF_COMPARE_EXCHANGE_DOUBLE] = (regs2[3] & (1 << 8 )) >> 8; - PF[PF_MMX_INSTRUCTIONS_AVAILABLE] = (regs2[3] & (1 << 23)) >> 23; - /* Check for OS support of SSE -- Is this used, and should it be sse1 or sse2? */ - /*len = sizeof(num); - ret = sysctlbyname("hw.instruction_sse", &num, &len, NULL, 0); - if (!ret) - PF[PF_XMMI_INSTRUCTIONS_AVAILABLE] = num;*/ - - if (regs[1] == AUTH && - regs[3] == ENTI && - regs[2] == CAMD) { - do_cpuid(0x80000000, regs); /* get vendor cpuid level */ - if (regs[0]>=0x80000001) { - do_cpuid(0x80000001, regs2); /* get vendor features */ - PF[PF_3DNOW_INSTRUCTIONS_AVAILABLE] = - (regs2[3] & (1 << 31 )) >> 31; - } + + PF[PF_FLOATING_POINT_EMULATED] = !CPUID_IsFeatureAvailable(CPU_FEATURE_FPU); + PF[PF_RDTSC_INSTRUCTION_AVAILABLE] = CPUID_IsFeatureAvailable(CPU_FEATURE_TIMESTAMP); + PF[PF_COMPARE_EXCHANGE_DOUBLE] = CPUID_IsFeatureAvailable(CPU_FEATURE_CMPXCHG8); + PF[PF_MMX_INSTRUCTIONS_AVAILABLE] = CPUID_IsFeatureAvailable(CPU_FEATURE_MMX); + /* Check for OS support of SSE -- Is this used, and should it be sse1 or sse2 + */ + if ((sse_lvl=OS_SSESupported())){ + if (sse_lvl >0)PF[PF_XMMI_INSTRUCTIONS_AVAILABLE] = CPUID_IsFeatureAvailable(CPU_FEATURE_SIMD); + if(sse_lvl>1) PF[PF_XMMI64_INSTRUCTIONS_AVAILABLE] = CPUID_IsFeatureAvailable(CPU_FEATURE_SIMD2); } + PF[PF_PAE_ENABLED] = CPUID_IsFeatureAvailable(CPU_FEATURE_PAE); + PF[PF_3DNOW_INSTRUCTIONS_AVAILABLE] = CPUID_IsFeatureAvailable(CPU_FEATURE_3DNOW); + + } - len = sizeof(num); - ret = sysctlbyname("hw.ncpu", &num, &len, NULL, 0); - if (!ret) - cachedsi.dwNumberOfProcessors = num; - } + + + cachedsi.dwNumberOfProcessors = get_processor_count( ); + memcpy(si,&cachedsi,sizeof(*si)); #else FIXME("not yet supported on this system\n");