1 | ; $Id: bs3-mode-CpuDetect.asm 98103 2023-01-17 14:15:46Z vboxsync $
|
---|
2 | ;; @file
|
---|
3 | ; BS3Kit - Bs3CpuDetect
|
---|
4 | ;
|
---|
5 |
|
---|
6 | ;
|
---|
7 | ; Copyright (C) 2007-2023 Oracle and/or its affiliates.
|
---|
8 | ;
|
---|
9 | ; This file is part of VirtualBox base platform packages, as
|
---|
10 | ; available from https://www.virtualbox.org.
|
---|
11 | ;
|
---|
12 | ; This program is free software; you can redistribute it and/or
|
---|
13 | ; modify it under the terms of the GNU General Public License
|
---|
14 | ; as published by the Free Software Foundation, in version 3 of the
|
---|
15 | ; License.
|
---|
16 | ;
|
---|
17 | ; This program is distributed in the hope that it will be useful, but
|
---|
18 | ; WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
19 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
20 | ; General Public License for more details.
|
---|
21 | ;
|
---|
22 | ; You should have received a copy of the GNU General Public License
|
---|
23 | ; along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
24 | ;
|
---|
25 | ; The contents of this file may alternatively be used under the terms
|
---|
26 | ; of the Common Development and Distribution License Version 1.0
|
---|
27 | ; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
|
---|
28 | ; in the VirtualBox distribution, in which case the provisions of the
|
---|
29 | ; CDDL are applicable instead of those of the GPL.
|
---|
30 | ;
|
---|
31 | ; You may elect to license modified versions of this file under the
|
---|
32 | ; terms and conditions of either the GPL or the CDDL or both.
|
---|
33 | ;
|
---|
34 | ; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
|
---|
35 | ;
|
---|
36 |
|
---|
37 | %include "bs3kit-template-header.mac"
|
---|
38 |
|
---|
39 | BS3_EXTERN_DATA16 g_uBs3CpuDetected
|
---|
40 |
|
---|
41 |
|
---|
42 | ;;
|
---|
43 | ; Rough CPU detection, mainly for detecting really old CPUs.
|
---|
44 | ;
|
---|
45 | ; A Bs3CpuDetectEx can be added if this is insufficient.
|
---|
46 | ;
|
---|
47 | ; @returns BS3CPU_xxx in xAX.
|
---|
48 | ; @cproto BS3_DECL(BS3CPU) Bs3CpuDetect(void);
|
---|
49 | ;
|
---|
50 | ; @uses xAX.
|
---|
51 | ;
|
---|
52 | ; @remarks ASSUMES we're in ring-0 when not in some kind of real mode.
|
---|
53 | ;
|
---|
54 | ; @note We put the real mode version of this code in the RMTEXT16 segment
|
---|
55 | ; to save space elsewhere. We generate a far call stub that goes
|
---|
56 | ; to the right segment.
|
---|
57 | ;
|
---|
58 | %if TMPL_MODE == BS3_MODE_RM
|
---|
59 | BS3_BEGIN_RMTEXT16
|
---|
60 | BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_FAR
|
---|
61 | %else
|
---|
62 | TMPL_BEGIN_TEXT
|
---|
63 | BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_HYBRID
|
---|
64 | %endif
|
---|
65 | CPU 8086
|
---|
66 | push xBP
|
---|
67 | mov xBP, xSP
|
---|
68 | pushf ; xBP - xCB*1
|
---|
69 | push xCX ; xBP - xCB*2
|
---|
70 | push xDX ; xBP - xCB*3
|
---|
71 | push xBX ; xBP - xCB*4
|
---|
72 | sub xSP, 20h ; xBP - xCB*4 - 20h
|
---|
73 |
|
---|
74 | %ifndef TMPL_CMN_PAGING
|
---|
75 | %ifdef TMPL_RM
|
---|
76 | %if 1 ; this is simpler
|
---|
77 | ;
|
---|
78 | ; FLAGS bits 15:12 are always set on 8086, 8088, V20, V30, 80186, and
|
---|
79 | ; 80188. FLAGS bit 15 is always zero on 286+, whereas bit 14 is NT and
|
---|
80 | ; bits 13:12 are IOPL.
|
---|
81 | ;
|
---|
82 | test byte [xBP - xCB + 1], 80h ; Top byte of saved flags.
|
---|
83 | jz .286plus
|
---|
84 | %else
|
---|
85 | ;
|
---|
86 | ; When executing 'PUSH SP' the 8086, 8088, V20, V30, 80186, and 80188
|
---|
87 | ; should be pushing the updated SP value instead of the initial one.
|
---|
88 | ;
|
---|
89 | push xSP
|
---|
90 | pop xAX
|
---|
91 | cmp xAX, xSP
|
---|
92 | je .286plus
|
---|
93 | %endif
|
---|
94 |
|
---|
95 | ;
|
---|
96 | ; Older than 286.
|
---|
97 | ;
|
---|
98 | ; Detect 8086/8088/V20/V30 vs. 80186/80188 by checking for pre 80186
|
---|
99 | ; shift behavior. the 80186/188 and later will mask the CL value according
|
---|
100 | ; to the width of the destination register, whereas 8086/88 and V20/30 will
|
---|
101 | ; perform the exact number of shifts specified.
|
---|
102 | ;
|
---|
103 | mov cl, 20h ; Shift count; 80186/88 and later will mask this by 0x1f (or 0xf)?
|
---|
104 | mov dx, 7fh
|
---|
105 | shl dx, cl
|
---|
106 | cmp dx, 7fh ; If no change, this is a 80186/88.
|
---|
107 | mov xAX, BS3CPU_80186
|
---|
108 | je .return
|
---|
109 |
|
---|
110 | ;
|
---|
111 | ; Detect 8086/88 vs V20/30 by exploiting undocumented POP CS encoding
|
---|
112 | ; that was redefined on V20/30 to SET1.
|
---|
113 | ;
|
---|
114 | xor ax, ax ; clear
|
---|
115 | push cs
|
---|
116 | db 0fh ; 8086/88: pop cs V20/30: set1 bl,cl
|
---|
117 | db 14h, 3ch ; 8086/88: add al, 3ch
|
---|
118 | ; 8086/88: al = 3ch V20/30: al = 0, cs on stack, bl modified.
|
---|
119 | cmp al, 3ch
|
---|
120 | jne .is_v20_or_v30
|
---|
121 | mov xAX, BS3CPU_8086
|
---|
122 | jmp .return
|
---|
123 |
|
---|
124 | .is_v20_or_v30:
|
---|
125 | pop xCX ; unclaimed CS
|
---|
126 | mov xAX, BS3CPU_V20
|
---|
127 | jmp .return
|
---|
128 |
|
---|
129 | %endif ; TMPL_RM
|
---|
130 |
|
---|
131 | CPU 286
|
---|
132 | .286plus:
|
---|
133 | ;
|
---|
134 | ; The 4th bit of the machine status word / CR0 indicates the precense
|
---|
135 | ; of a 80387 or later co-processor (a 80287+80386 => ET=0). 486 and
|
---|
136 | ; later should be hardcoding this to 1, according to the documentation
|
---|
137 | ; (need to test on 486SX). The initial idea here then would be to
|
---|
138 | ; assume 386+ if ET=1.
|
---|
139 | ;
|
---|
140 | ; The second idea was to check whether any reserved bits are set,
|
---|
141 | ; because the 286 here has bits 4 thru 15 all set. Unfortunately, it
|
---|
142 | ; turned out the 386SX and AMD 486DX-40 also sets bits 4 thru 15 when
|
---|
143 | ; using SMSW. So, nothing conclusive to distinguish 386 from 286, but
|
---|
144 | ; we've probably got a safe 486+ detection here.
|
---|
145 | ;
|
---|
146 | ;; @todo check if LOADALL can set any of the reserved bits on a 286 or 386.
|
---|
147 | smsw ax
|
---|
148 | test ax, ~(X86_CR0_PE | X86_CR0_MP | X86_CR0_EM | X86_CR0_TS | X86_CR0_ET | X86_CR0_NE)
|
---|
149 | jz .486plus
|
---|
150 |
|
---|
151 | ;
|
---|
152 | ; The 286 stores 0xff in the high byte of the SIDT and SGDT base
|
---|
153 | ; address (since it only did 24-bit addressing and the top 8-bit was
|
---|
154 | ; reserved for the 386). ASSUMES low IDT (which is the case for BS3Kit).
|
---|
155 | ;
|
---|
156 | sidt [xBP - xCB*4 - 20h]
|
---|
157 | cmp byte [xBP - xCB*4 - 20h + 2 + 3], 0ffh
|
---|
158 | jne .386plus
|
---|
159 |
|
---|
160 | %if 0
|
---|
161 | ;
|
---|
162 | ; Detect 80286 by checking whether the IOPL and NT bits of EFLAGS can be
|
---|
163 | ; modified or not. There are different accounts of these bits. Dr.Dobb's
|
---|
164 | ; (http://www.drdobbs.com/embedded-systems/processor-detection-schemes/184409011)
|
---|
165 | ; say they are undefined on 286es and will always be zero. Whereas Intel
|
---|
166 | ; iAPX 286 Programmer's Reference Manual (both order #210498-001 and
|
---|
167 | ; #210498-003) documents both IOPL and NT, but with comment 4 on page
|
---|
168 | ; C-43 stating that they cannot be POPFed in real mode and will both
|
---|
169 | ; remain 0. This is different from the 386+, where the NT flag isn't
|
---|
170 | ; privileged according to page 3-37 in #230985-003. Later Intel docs
|
---|
171 | ; (#235383-052US, page 4-192) documents real mode as taking both NT and
|
---|
172 | ; IOPL from what POPF reads off the stack - which is the behavior
|
---|
173 | ; observed a 386SX here.
|
---|
174 | ;
|
---|
175 | test al, X86_CR0_PE ; This flag test doesn't work in protected mode, ...
|
---|
176 | jnz .386plus ; ... so ASSUME 386plus if in PE for now.
|
---|
177 |
|
---|
178 | pushf ; Save a copy of the original flags for restoring IF.
|
---|
179 | pushf
|
---|
180 | pop ax
|
---|
181 | xor ax, X86_EFL_IOPL | X86_EFL_NT ; Try modify IOPL and NT.
|
---|
182 | and ax, ~X86_EFL_IF ; Try clear IF.
|
---|
183 | push ax ; Load modified flags.
|
---|
184 | popf
|
---|
185 | pushf ; Get actual flags.
|
---|
186 | pop dx
|
---|
187 | popf ; Restore IF, IOPL and NT.
|
---|
188 | cmp ax, dx
|
---|
189 | je .386plus ; If any of the flags are set, we're on 386+.
|
---|
190 |
|
---|
191 | ; While we could in theory be in v8086 mode at this point and be fooled
|
---|
192 | ; by a flaky POPF implementation, we assume this isn't the case in our
|
---|
193 | ; execution environment.
|
---|
194 | %endif
|
---|
195 | .is_286:
|
---|
196 | mov ax, BS3CPU_80286
|
---|
197 | jmp .return
|
---|
198 | %endif ; !TMPL_CMN_PAGING
|
---|
199 |
|
---|
200 | CPU 386
|
---|
201 | .386plus:
|
---|
202 | .486plus:
|
---|
203 | ;
|
---|
204 | ; Check for CPUID and AC. The former flag indicates CPUID support, the
|
---|
205 | ; latter was introduced with the 486.
|
---|
206 | ;
|
---|
207 | mov ebx, esp ; Save esp.
|
---|
208 | and esp, 0fffch ; Clear high word and don't trigger ACs.
|
---|
209 | pushfd
|
---|
210 | mov eax, [esp] ; eax = original EFLAGS.
|
---|
211 | xor dword [esp], X86_EFL_ID | X86_EFL_AC ; Flip the ID and AC flags.
|
---|
212 | popfd ; Load modified flags.
|
---|
213 | pushfd ; Save actual flags.
|
---|
214 | xchg eax, [esp] ; Switch, so the stack has the original flags.
|
---|
215 | xor eax, [esp] ; Calc changed flags.
|
---|
216 | popf ; Restore EFLAGS.
|
---|
217 | mov esp, ebx ; Restore possibly unaligned ESP.
|
---|
218 | test eax, X86_EFL_ID
|
---|
219 | jnz .have_cpuid ; If ID changed, we've got CPUID.
|
---|
220 | test eax, X86_EFL_AC
|
---|
221 | mov xAX, BS3CPU_80486
|
---|
222 | jnz .return ; If AC changed, we've got a 486 without CPUID (or similar).
|
---|
223 | mov xAX, BS3CPU_80386
|
---|
224 | jmp .return
|
---|
225 |
|
---|
226 | CPU 586
|
---|
227 | .have_cpuid:
|
---|
228 | ;
|
---|
229 | ; Do a very simple minded check here using the (standard) family field.
|
---|
230 | ; While here, we also check for PAE.
|
---|
231 | ;
|
---|
232 | mov eax, 1
|
---|
233 | cpuid
|
---|
234 |
|
---|
235 | ; Calc the extended family and model values before we mess up EAX.
|
---|
236 | mov cl, ah
|
---|
237 | and cl, 0fh
|
---|
238 | cmp cl, 0fh
|
---|
239 | jnz .not_extended_family
|
---|
240 | mov ecx, eax
|
---|
241 | shr ecx, 20
|
---|
242 | and cl, 7fh
|
---|
243 | add cl, 0fh
|
---|
244 | .not_extended_family: ; cl = family
|
---|
245 | mov ch, al
|
---|
246 | shr ch, 4
|
---|
247 | cmp cl, 0fh
|
---|
248 | jae .extended_model
|
---|
249 | cmp cl, 06h ; actually only intel, but we'll let this slip for now.
|
---|
250 | jne .done_model
|
---|
251 | .extended_model:
|
---|
252 | shr eax, 12
|
---|
253 | and al, 0f0h
|
---|
254 | or ch, al
|
---|
255 | .done_model: ; ch = model
|
---|
256 |
|
---|
257 | ; Start assembling return flags, checking for PSE + PAE.
|
---|
258 | mov eax, X86_CPUID_FEATURE_EDX_PSE | X86_CPUID_FEATURE_EDX_PAE
|
---|
259 | and eax, edx
|
---|
260 | mov ah, al
|
---|
261 | AssertCompile(X86_CPUID_FEATURE_EDX_PAE_BIT > BS3CPU_F_PAE_BIT - 8) ; 6 vs 10-8=2
|
---|
262 | and al, X86_CPUID_FEATURE_EDX_PAE
|
---|
263 | shr al, X86_CPUID_FEATURE_EDX_PAE_BIT - (BS3CPU_F_PAE_BIT - 8)
|
---|
264 | AssertCompile(X86_CPUID_FEATURE_EDX_PSE_BIT == BS3CPU_F_PSE_BIT - 8) ; 3 vs 11-8=3
|
---|
265 | and ah, X86_CPUID_FEATURE_EDX_PSE
|
---|
266 | or ah, al
|
---|
267 | or ah, (BS3CPU_F_CPUID >> 8)
|
---|
268 |
|
---|
269 | ; Add the CPU type based on the family and model values.
|
---|
270 | cmp cl, 6
|
---|
271 | jne .not_family_06h
|
---|
272 | mov al, BS3CPU_PPro
|
---|
273 | cmp ch, 1
|
---|
274 | jbe .return
|
---|
275 | mov al, BS3CPU_PProOrNewer
|
---|
276 | jmp .NewerThanPPro
|
---|
277 |
|
---|
278 | .not_family_06h:
|
---|
279 | mov al, BS3CPU_PProOrNewer
|
---|
280 | ja .NewerThanPPro
|
---|
281 | cmp cl, 5
|
---|
282 | mov al, BS3CPU_Pentium
|
---|
283 | je .return
|
---|
284 | cmp cl, 4
|
---|
285 | mov al, BS3CPU_80486
|
---|
286 | je .return
|
---|
287 | cmp cl, 3
|
---|
288 | mov al, BS3CPU_80386
|
---|
289 | je .return
|
---|
290 |
|
---|
291 | .NewerThanPPro:
|
---|
292 |
|
---|
293 | ; Check for extended leaves and long mode.
|
---|
294 | push xAX ; save PAE+PProOrNewer
|
---|
295 | mov eax, 0x80000000
|
---|
296 | cpuid
|
---|
297 | sub eax, 0x80000001 ; Minimum leaf 0x80000001
|
---|
298 | cmp eax, 0x00010000 ; At most 0x10000 leaves.
|
---|
299 | ja .no_ext_leaves
|
---|
300 |
|
---|
301 | mov eax, 0x80000001
|
---|
302 | cpuid
|
---|
303 | pop xAX ; restore PAE+PProOrNewer
|
---|
304 | test edx, X86_CPUID_EXT_FEATURE_EDX_LONG_MODE
|
---|
305 | jz .no_long_mode
|
---|
306 | or ah, ((BS3CPU_F_CPUID_EXT_LEAVES | BS3CPU_F_LONG_MODE) >> 8)
|
---|
307 | jmp .no_check_for_nx
|
---|
308 | .no_long_mode:
|
---|
309 | or ah, (BS3CPU_F_CPUID_EXT_LEAVES >> 8)
|
---|
310 | .no_check_for_nx:
|
---|
311 | test edx, X86_CPUID_EXT_FEATURE_EDX_NX
|
---|
312 | jz .return
|
---|
313 | or ax, BS3CPU_F_NX
|
---|
314 | jmp .return
|
---|
315 |
|
---|
316 | .no_ext_leaves:
|
---|
317 | pop xAX ; restore PAE+PProOrNewer
|
---|
318 |
|
---|
319 | CPU 8086
|
---|
320 | .return:
|
---|
321 | ;
|
---|
322 | ; Save the return value.
|
---|
323 | ;
|
---|
324 | mov [BS3_DATA16_WRT(g_uBs3CpuDetected)], ax
|
---|
325 |
|
---|
326 | ;
|
---|
327 | ; Epilogue.
|
---|
328 | ;
|
---|
329 | add xSP, 20h
|
---|
330 | pop xBX
|
---|
331 | pop xDX
|
---|
332 | pop xCX
|
---|
333 | popf
|
---|
334 | pop xBP
|
---|
335 | BS3_HYBRID_RET
|
---|
336 |
|
---|
337 | BS3_PROC_END_MODE Bs3CpuDetect
|
---|
338 |
|
---|
339 |
|
---|
340 | %if TMPL_MODE == BS3_MODE_RM
|
---|
341 | BS3_BEGIN_TEXT16_NEARSTUBS
|
---|
342 | BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_NEAR
|
---|
343 | call far TMPL_FAR_NM(Bs3CpuDetect)
|
---|
344 | ret
|
---|
345 | BS3_PROC_END_MODE Bs3CpuDetect
|
---|
346 | %endif
|
---|
347 |
|
---|