1 |
/* NSIS plug-in for getting a bit of CPU information. |
2 |
Version 1.2, July 2003. |
3 |
Typed and clicked by Peter Mason, CSIRO DEM MMTG. mailto://peter.mason@csiro.au. |
4 |
The MHz timing was done using code that was pretty much copied from Pavlos Touboulidis' CPUTEST code. |
5 |
The lean exception handler wrapped around the timing code was done using Jeremy Gordon's tutorial on writing |
6 |
lightweight win32 exception handlers in assembler. (His web-page is www.GoDevTool.com.) |
7 |
The CPUID stuff was done using Intel and AMD's manuals on what CPUID means in their respective microcosms. |
8 |
Best viewed with <TAB> == 2 spaces. |
9 |
|
10 |
There's only one routine here - tell() - and its output is a string on the NSIS stack. |
11 |
This string always has the same fields in exactly the same place. (Easier to extract values that way.) It looks like this: |
12 |
|
13 |
INTELP=d AMD=add PPRO=b MMX=d SSE=b SSE2=b 3DNOW=d ARCH=dd LEVEL=dd NCPU=dd MHZ=ddddd RAM=dddd |
14 |
|
15 |
Here, "d" means a decimal digit (0..9), "a" means an alphabetic character (A..Z) and "b" means a boolean digit (0 or 1). |
16 |
ITELP: Values range [0..4]. |
17 |
0: Not a genuine Intel CPU (or a very, VERY old one). |
18 |
1: Pentium or Pentium with MMX. (Check the MMX field if you want to know about the CPU's MMX support.) |
19 |
2: Pentium Pro, II or Celeron. (May or may not have MMX - PPros don't, the others do. Check the MMX field.) |
20 |
3: Pentium III or P3 (old) Xeon. (Always has MMX and SSE.) |
21 |
4: Pentium IV or (new) Xeon. (Always has MMX, SSE and SSE2.) |
22 |
AMD: A bit more complicated... |
23 |
000: Not an authentic AMD CPU (or a very old one). |
24 |
Kdd: An old K-series. "dd" is either 05 for a K5 or 06 for a K6. |
25 |
(Pentium compatible. K5s have no MMX or 3DNOW. K6s have standard MMX, and later models have basic 3DNOW.) |
26 |
Add: An Athlon or a Duron. "dd" is the model number (goes from 01 to 10). |
27 |
(Pentium II compatible. All of these have extended MMX and extended 3DNOW. None have any SSE.) |
28 |
Odd: An opteron. "dd" gives the model number. |
29 |
(Pentium IV compatible. This CPU's got everything, it seems.) |
30 |
PPRO: Values range [0..1]. |
31 |
0: Not compatible with the Intel Pentium Pro processor. |
32 |
1: Compatible with the Intel Pentium Pro processor. |
33 |
MMX: Values range [0..2]. |
34 |
0: No MMX support. |
35 |
1: Standard Intel MMX support. |
36 |
2: Standard MMX support plus AMD MMX extensions. |
37 |
SSE: Values range [0..1]. |
38 |
0: No SSE support. |
39 |
1: Supports SSE (Intel's Streaming SIMD extensions, P3-style). |
40 |
SSE2: Values range [0..1]. |
41 |
0: No SSE2 support. |
42 |
1: Supports SSE2 (Intel's Streaming SIMD extensions 2, P4-style). |
43 |
3DNOW: Values range [0..2]. |
44 |
0: No 3DNOW support. |
45 |
1: Standard AMD 3DNOW support. |
46 |
2: Standard 3DNOW support plus AMD 3DNOW extensions. |
47 |
ARCH: Values range [00..10]. |
48 |
00: 32-bit Intel or compatible |
49 |
01: MIPS (did NT 3.5, apparently) |
50 |
02: DEC Alpha. (Yes, DEC. I can't bring myself to call it COMPAQ.) |
51 |
03: PowerPC |
52 |
04: SHX (?) |
53 |
05: ARM (Acorn / Advanced Risc Machine, I presume. I don't think anyone's going to see this running Windows?) |
54 |
06: 64-bit Intel. |
55 |
07: 64-bit Alpha |
56 |
08: MSIL (?) |
57 |
09: 64-bit AMD |
58 |
10: 32-bit Intel doing Win64 (?) |
59 |
LEVEL: "Processor level", like what you see in the main processor environment variable. Sort-of useless, really. |
60 |
NCPU: The number of processors available. (Affected by that "Hyper" business that the new XEONs can do, I think.) |
61 |
MHZ: The CPU's internal clock speed in MHz (approx). |
62 |
RAM: The amount of RAM (physical memory) in megabytes (rounded). |
63 |
|
64 |
|
65 |
Compilation: |
66 |
/nologo /MT /W3 /vd0 /Og /Os /Oy /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /Fo"Release/" /Fd"Release/" /Zl /FD /c |
67 |
Linking: |
68 |
kernel32.lib user32.lib advapi32.lib /nologo /entry:"_DllMainCRTStartup" /dll /incremental:no /pdb:"Release/cpudesc.pdb" /machine:I386 /nodefaultlib /out:"Release/cpudesc.dll" /implib:"Release/cpudesc.lib" /MERGE:.rdata=.text /MERGE:.text=.text /MERGE:.reloc=.text /OPT:REF /FILEALIGN:512 |
69 |
|
70 |
*/ |
71 |
#include <windows.h> |
72 |
#include "../ExDLL/exdll.h" |
73 |
|
74 |
/*****************************************/ |
75 |
// Gets the MHz timing stored by Windows in the registry. Returns 0 MHz if there's a problem reading the expected registry value. |
76 |
// This is used as a fall-back for when the timer routine can't be run. I don't know if this registry value is stored consistently |
77 |
// for different versions of Windows. |
78 |
static int mhzfromreg(void) |
79 |
{ |
80 |
HKEY k; |
81 |
DWORD drv, ndrv=4; |
82 |
int rv=0; |
83 |
if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &k ) ) { |
84 |
if( ERROR_SUCCESS == RegQueryValueEx( k, "~MHz", 0, NULL, (LPBYTE)(&drv), &ndrv ) ) rv=(int)drv; |
85 |
RegCloseKey(k); |
86 |
} |
87 |
return rv; |
88 |
} |
89 |
/*****************************************/ |
90 |
// This lot pretty much lifted from Pavlos Touboulidis' CPUTEST code. |
91 |
// (Note _cdecl in case this isn't the default compilation setting. The assembly calling routine expects this protocol.) |
92 |
static void _cdecl delay(int foroverhead) |
93 |
{ |
94 |
LARGE_INTEGER c1, c2; |
95 |
__int64 x, y; |
96 |
if( !QueryPerformanceFrequency(&c1) ) return; |
97 |
//x=c1.QuadPart>>4; //hard coded for 62.5ms interval |
98 |
__asm { //avoid bringing in the CRTL for dividing 64-bit c1 by 16 (to get an interval of 62.5 ms) |
99 |
push eax |
100 |
mov eax,c1.HighPart |
101 |
shrd c1.LowPart,eax,4 |
102 |
shr eax,4 //yes, the SHRD above won't have changed EAX. |
103 |
mov c1.HighPart,eax |
104 |
pop eax |
105 |
} |
106 |
x=c1.QuadPart; |
107 |
QueryPerformanceCounter(&c1); |
108 |
do { |
109 |
QueryPerformanceCounter(&c2); |
110 |
y=c2.QuadPart-c1.QuadPart; |
111 |
} while( foroverhead ? (y==x) : (y<x) ); |
112 |
return; |
113 |
} |
114 |
/**************/ |
115 |
/* Unfortunately, there's no decent way (in non-privileged mode) to tell if the CPU is allowed to execute the RDTSC |
116 |
instruction in non-privileged mode. The relevant status bit is bit 2 in "CR4" (control register 4), and we can't |
117 |
even look at CR4 in user mode. |
118 |
So I have resorted to the indiscrete way of letting it f.. er, throw an exception if it must... |
119 |
In the event of an exception (due to disallowed use of RDTSC), the exception will get caught and the MHz value |
120 |
pulled out of the registry earlier will silently get returned instead of a timed value. The exception handler |
121 |
is very simple-minded. It doesn't check what sort of exception occurred, it just "handles" it by jumping to the |
122 |
end of the timer code. (If it gets called, it's assumed that it was due to a disallowed call to RDTSC.) |
123 |
BTW, this code seems to produce a good answer on a dual CPU PC. |
124 |
*/ |
125 |
static void mhzfromtimer(int *mhz) |
126 |
{ |
127 |
HANDLE hproc=GetCurrentProcess(), hthr=GetCurrentThread(); |
128 |
DWORD oldpc=GetPriorityClass(hproc); // old priority class |
129 |
DWORD eax0, edx0, tmhz=*mhz; |
130 |
int oldtp=GetThreadPriority(hthr); // old thread priority |
131 |
//if( GetProcessAffinityMask( hproc, &pam, &sam ) && (pam>1) ) { |
132 |
// tam=SetThreadAffinityMask( hthr, 1 ); //lock onto the primary CPU if 2+ CPUs |
133 |
// Sleep(1); //maybe we weren't on the primary CPU? Hopefully, we will be after this. (I don't know if SetThreadAffinityMask() does the necessary.) |
134 |
//} |
135 |
SetPriorityClass( hproc, HIGH_PRIORITY_CLASS ); //that should be sufficient |
136 |
SetThreadPriority( hthr, THREAD_PRIORITY_TIME_CRITICAL ); //...and that |
137 |
__asm { |
138 |
pushad //just save all general registers |
139 |
push offset term //"safe place" for exception handler's return (our own addition to the structure) |
140 |
push offset exh //exception handler's start (expected in structure) |
141 |
push dword ptr fs:[0] //becomes "next handler" (expected in structure) |
142 |
mov fs:[0], esp //rig the top ERR structure to be for our handler (we've just built its structure here on the stack) |
143 |
RDTSC //(Start of the timing job - what we came here for) |
144 |
mov esi,eax |
145 |
mov edi,edx |
146 |
push 0 |
147 |
call delay //delay(0) |
148 |
pop ecx |
149 |
RDTSC |
150 |
sub eax,esi |
151 |
sbb edx,edi |
152 |
mov eax0,eax |
153 |
mov edx0,edx //that's the main count in edx0:eax0 |
154 |
RDTSC |
155 |
mov esi,eax |
156 |
mov edi,edx |
157 |
push 1 |
158 |
call delay //delay(1) |
159 |
pop ecx |
160 |
RDTSC |
161 |
sub eax,esi |
162 |
sbb edx,edi |
163 |
sub eax0,eax |
164 |
sbb edx0,edx //that's the overhead count subtracted from edx0:eax0 |
165 |
mov eax,eax0 |
166 |
mov edx,edx0 |
167 |
mov ecx,62500 //timed over 62.5 ms |
168 |
div ecx |
169 |
mov tmhz,eax //..and THERE'S OUR RESULT |
170 |
term: pop dword ptr fs:[0] //reinstate old top handler |
171 |
add esp,8 //(chuck away addresses pushed onto the stack for our handler) |
172 |
popad //restore all general registers |
173 |
jmp fin |
174 |
exh: push edi //Start of exception handler |
175 |
push esi |
176 |
mov edi, [esp+10h] //our ERR structure |
177 |
mov esi, [esp+14h] //context structure |
178 |
mov [esi+0C4h], edi //insert new esp into the context structure |
179 |
mov eax, [edi+8] //address of safe place "term" that we stored in our ERR structure |
180 |
mov [esi+0B8h], eax //insert new eip |
181 |
xor eax, eax //return code 0 means we've handled it |
182 |
pop esi |
183 |
pop edi |
184 |
ret //End of exception handler |
185 |
fin: |
186 |
} |
187 |
*mhz=tmhz; |
188 |
SetThreadPriority( hthr, oldtp ); //restore it |
189 |
SetPriorityClass( hproc, oldpc ); //...and it |
190 |
//if(tam) SetThreadAffinityMask( hthr, tam ); //..and it |
191 |
return; |
192 |
} |
193 |
/*****************************************/ |
194 |
void __declspec(dllexport) tell(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) |
195 |
{ |
196 |
EXDLL_INIT(); |
197 |
{ |
198 |
char ostr[256]; |
199 |
SYSTEM_INFO si; |
200 |
MEMORYSTATUS ms; |
201 |
unsigned int mhz=0, ram=0; |
202 |
char arch=0, level=0, pprocompat=0, hasmmx=0, has3dnow=0, hassse=0, hassse2=0, intelpentium=0, amd=0, amdlet='0', ncpu=1, tsc=0; |
203 |
GlobalMemoryStatus(&ms); |
204 |
if( 0xfff90000 <= (ram=ms.dwTotalPhys) ) ram=4*1024; //Just say it's 4GB! |
205 |
else ram = (ram+512*1024) / (1024*1024); //report megabytes (rounded) |
206 |
mhz=mhzfromreg(); |
207 |
GetSystemInfo( &si ); |
208 |
arch =(char)si.wProcessorArchitecture; //type code (e.g., 0==intel) |
209 |
level =(char)si.wProcessorLevel; //CPU "level" |
210 |
ncpu =(char)si.dwNumberOfProcessors; |
211 |
if(!ncpu) ncpu=1; |
212 |
hasmmx =IsProcessorFeaturePresent( PF_MMX_INSTRUCTIONS_AVAILABLE ); //just about everything these days? |
213 |
has3dnow=IsProcessorFeaturePresent( PF_3DNOW_INSTRUCTIONS_AVAILABLE ); //for some AMDs? |
214 |
hassse =IsProcessorFeaturePresent( PF_XMMI_INSTRUCTIONS_AVAILABLE ); //Pentium III or better? |
215 |
hassse2 =IsProcessorFeaturePresent( PF_XMMI64_INSTRUCTIONS_AVAILABLE ); //umm? (This doesn't seem to work for P4s) |
216 |
if( (0==arch) || (6==arch) || (7==arch) || (9==arch) || (10==arch) ) { //Intel compatible architecture - let's get some better detail |
217 |
__asm { |
218 |
push eax |
219 |
push ebx |
220 |
push ecx |
221 |
push edx |
222 |
mov ebx,200000h |
223 |
pushfd |
224 |
pop eax |
225 |
mov ecx,eax ;save the flags reg's original contents |
226 |
xor eax,ebx ;try to change bit 21 in the flags register |
227 |
push eax |
228 |
popfd ;(that's our attempt to change the flags reg) |
229 |
pushfd |
230 |
pop eax ;now let's see if it really got changed |
231 |
xor eax,ecx |
232 |
and eax,ebx ;test just for a change of bit 21 (being a.r. here) |
233 |
jz bye ;if our change didn't stick then we can't execute the CPUID instruction |
234 |
xor eax,eax |
235 |
cpuid ;is it a real Intel or AMD CPU? |
236 |
cmp ebx,756E6547h ;"Genu" |
237 |
jnz ckamd |
238 |
cmp edx,49656E69h ;"ineI" |
239 |
jnz bye |
240 |
cmp ecx,6C65746Eh ;"ntel" |
241 |
jnz bye |
242 |
mov byte ptr intelpentium, 1 ;assume vanilla pentium for now |
243 |
jmp cont1 |
244 |
ckamd: cmp ebx,68747541h ;"Auth" |
245 |
jnz bye |
246 |
cmp edx,69746E65h ;"enti" |
247 |
jnz bye |
248 |
cmp ecx,444D4163h ;"cAMD" |
249 |
jnz bye |
250 |
mov byte ptr amd, 5 |
251 |
mov byte ptr amdlet, 'K' ;assume a K5 for the time being |
252 |
cont1: xor eax,eax |
253 |
mov byte ptr hasmmx, al |
254 |
mov byte ptr has3dnow, al |
255 |
mov byte ptr hassse, al |
256 |
mov byte ptr hassse2, al ;we'll be revising our opinions of mmx, 3dnow, sse and sse2 |
257 |
inc eax |
258 |
cpuid ;get some detail |
259 |
mov cl,10h ;this bit is set if the CPU has the RDTSC instruction |
260 |
and cl,dl |
261 |
jz notsc |
262 |
;mov ecx,CR4 ;can't do it - it's a privileged instruction |
263 |
;and cl,4 |
264 |
;jnz notsc |
265 |
inc byte ptr tsc ;MAYBE - won't work if bit 2 of CR4 is set, but we could only check that if we were running at privilege level 0 |
266 |
notsc: mov ecx,800000h ;this bit is set if the CPU does MMX |
267 |
and ecx,edx |
268 |
jz nommx |
269 |
inc byte ptr hasmmx |
270 |
nommx: xor bl,bl |
271 |
mov ecx,2000000h ;this bit is set if the CPU does SSE |
272 |
and ecx,edx |
273 |
jz nosse1 |
274 |
inc bl |
275 |
inc byte ptr hassse ;P3-style SSE |
276 |
nosse1: mov ecx,4000000h ;this bit is set if the CPU does SSE2 |
277 |
and ecx,edx |
278 |
jz nosse2 |
279 |
inc bl |
280 |
inc byte ptr hassse2 ;P4-style SSE2 |
281 |
nosse2: cmp byte ptr amd, 0 |
282 |
jnz amd2 ;further AMD-specific checks elsewhere |
283 |
shr eax,8 ;further Intel-specific cheks here... |
284 |
and al,0Fh ;get the family number |
285 |
cmp al,6h |
286 |
jb bye |
287 |
inc byte ptr intelpentium ;so far it looks like an old PPro, old Celeron, old P2 or better |
288 |
inc byte ptr pprocompat |
289 |
cmp bl,0 |
290 |
jz bye ;no SSE - it's an old thing and we're done |
291 |
inc byte ptr intelpentium ;if it's got some SSE support then so far it looks like a P3 or better |
292 |
cmp al,0Fh |
293 |
jnz bye |
294 |
inc byte ptr intelpentium ;you know, it looks like a P4 |
295 |
jmp bye |
296 |
amd2: mov ecx,1000000h ;this bit is set if the CPU does FXSR |
297 |
and ecx,edx ;dunno about Intel, but with AMD we should apparently check both bits 24 and 25 for SSE. |
298 |
jnz yessse |
299 |
mov byte ptr hassse, 0 ;turn off SSE if the CPU doesn't do FXSR (in addition to SSE) |
300 |
yessse: mov dl,0fh ;further AMD checks... |
301 |
mov ebx,eax |
302 |
shr eax,4 |
303 |
and al,dl |
304 |
cmp al,dl |
305 |
jnz noxm ;model number is complete as-is |
306 |
and ah,0f0h ;(extended model number) |
307 |
add al,ah ;full model number now in AL |
308 |
noxm: shr ebx,8 |
309 |
and bl,dl |
310 |
cmp bl,dl |
311 |
jnz noxf ;family number is complete as-is |
312 |
mov edx,ebx |
313 |
shr edx,12 |
314 |
and dl,0ffh ;(extended family number) |
315 |
add bl,dl ;full family number now in bl |
316 |
noxf: cmp bl,5 |
317 |
ja amda ;looks like an athlon or better |
318 |
cmp al,6 |
319 |
jb bye ;looks like a k5 - we're done |
320 |
mov byte ptr amd, 6 ;looks like a k6 |
321 |
jmp amd4 |
322 |
amda: inc byte ptr pprocompat ;athlons / durons are Pentium II compatible |
323 |
cmp bl,14 |
324 |
ja amdo ;looks like an opteron |
325 |
mov byte ptr amdlet, 'A' ;looks like an athlon / duron |
326 |
jmp amd3 |
327 |
amdo: mov byte ptr amdlet, 'O' ;(looks like an opteron) |
328 |
amd3: mov byte ptr amd, al ;report the model number for athlons / durons / opterons |
329 |
amd4: mov eax,80000001h |
330 |
cpuid ;get some AMD specifics |
331 |
mov ecx,40000h |
332 |
and ecx,edx |
333 |
jz amd5 |
334 |
inc byte ptr hasmmx ;it's got AMD MMX extensions |
335 |
amd5: mov ecx,8000000h |
336 |
mov ebx,ecx |
337 |
and ecx,edx |
338 |
jz bye |
339 |
inc byte ptr has3dnow ;it's got basic 3DNOW |
340 |
shr ebx,1 |
341 |
and ebx,edx |
342 |
jz bye |
343 |
inc byte ptr has3dnow ;it's got extended 3DNOW |
344 |
bye: pop edx |
345 |
pop ecx |
346 |
pop ebx |
347 |
pop eax |
348 |
} |
349 |
if(tsc) mhzfromtimer(&mhz); //get CPU MHz via timing if it appears that we can execute the RDTSC instruction |
350 |
} |
351 |
wsprintf( ostr, "INTELP=%1d AMD=%c%2.2d PPRO=%1d MMX=%1d SSE=%1d SSE2=%1d 3DNOW=%1d ARCH=%2.2d LEVEL=%2.2d NCPU=%2.2d MHZ=%5.5d RAM=%4.4d", |
352 |
intelpentium, amdlet, amd, pprocompat, hasmmx, hassse, hassse2, has3dnow, arch, level, ncpu, mhz, ram ); |
353 |
pushstring(ostr); |
354 |
} |
355 |
return; |
356 |
} |
357 |
/*****************************************/ |
358 |
BOOL WINAPI _DllMainCRTStartup(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) |
359 |
{ |
360 |
hInst=hInst; ul_reason_for_call=ul_reason_for_call; lpReserved=lpReserved; |
361 |
return TRUE; |
362 |
} |
363 |
/*****************************************/ |