summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-CpuDetect.asm
blob: b54cba28bbcc0939ca6c1b9647ef164064401509 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
; $Id: bs3-mode-CpuDetect.asm $
;; @file
; BS3Kit - Bs3CpuDetect
;

;
; Copyright (C) 2007-2023 Oracle and/or its affiliates.
;
; This file is part of VirtualBox base platform packages, as
; available from https://www.virtualbox.org.
;
; This program is free software; you can redistribute it and/or
; modify it under the terms of the GNU General Public License
; as published by the Free Software Foundation, in version 3 of the
; License.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
; General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program; if not, see <https://www.gnu.org/licenses>.
;
; The contents of this file may alternatively be used under the terms
; of the Common Development and Distribution License Version 1.0
; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
; in the VirtualBox distribution, in which case the provisions of the
; CDDL are applicable instead of those of the GPL.
;
; You may elect to license modified versions of this file under the
; terms and conditions of either the GPL or the CDDL or both.
;
; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
;

%include "bs3kit-template-header.mac"

BS3_EXTERN_DATA16 g_uBs3CpuDetected


;;
; Rough CPU detection, mainly for detecting really old CPUs.
;
; A Bs3CpuDetectEx can be added if this is insufficient.
;
; @returns  BS3CPU_xxx in xAX.
; @cproto   BS3_DECL(BS3CPU) Bs3CpuDetect(void);
;
; @uses     xAX.
;
; @remarks  ASSUMES we're in ring-0 when not in some kind of real mode.
;
; @note     We put the real mode version of this code in the RMTEXT16 segment
;           to save space elsewhere.  We generate a far call stub that goes
;           to the right segment.
;
%if TMPL_MODE == BS3_MODE_RM
BS3_BEGIN_RMTEXT16
BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_FAR
%else
TMPL_BEGIN_TEXT
BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_HYBRID
%endif
CPU 8086
        push    xBP
        mov     xBP, xSP
        pushf                           ; xBP - xCB*1
        push    xCX                     ; xBP - xCB*2
        push    xDX                     ; xBP - xCB*3
        push    xBX                     ; xBP - xCB*4
        sub     xSP, 20h                ; xBP - xCB*4 - 20h

%ifndef TMPL_CMN_PAGING
 %ifdef TMPL_RM
  %if 1                 ; this is simpler
        ;
        ; FLAGS bits 15:12 are always set on 8086, 8088, V20, V30, 80186, and
        ; 80188. FLAGS bit 15 is always zero on 286+, whereas bit 14 is NT and
        ; bits 13:12 are IOPL.
        ;
        test    byte [xBP - xCB + 1], 80h ; Top byte of saved flags.
        jz      .286plus
  %else
        ;
        ; When executing 'PUSH SP' the 8086, 8088, V20, V30, 80186, and 80188
        ; should be pushing the updated SP value instead of the initial one.
        ;
        push    xSP
        pop     xAX
        cmp     xAX, xSP
        je      .286plus
  %endif

        ;
        ; Older than 286.
        ;
        ; Detect 8086/8088/V20/V30 vs. 80186/80188 by checking for pre 80186
        ; shift behavior.  the 80186/188 and later will mask the CL value according
        ; to the width of the destination register, whereas 8086/88 and V20/30 will
        ; perform the exact number of shifts specified.
        ;
        mov     cl, 20h                 ; Shift count; 80186/88 and later will mask this by 0x1f (or 0xf)?
        mov     dx, 7fh
        shl     dx, cl
        cmp     dx, 7fh                 ; If no change, this is a 80186/88.
        mov     xAX, BS3CPU_80186
        je      .return

        ;
        ; Detect 8086/88 vs V20/30 by exploiting undocumented POP CS encoding
        ; that was redefined on V20/30 to SET1.
        ;
        xor     ax, ax                  ; clear
        push    cs
        db      0fh                     ; 8086/88: pop cs       V20/30: set1 bl,cl
        db      14h, 3ch                ; 8086/88: add al, 3ch
                                        ; 8086/88: al = 3ch     V20/30: al = 0, cs on stack, bl modified.
        cmp     al, 3ch
        jne     .is_v20_or_v30
        mov     xAX, BS3CPU_8086
        jmp     .return

.is_v20_or_v30:
        pop     xCX                     ; unclaimed CS
        mov     xAX, BS3CPU_V20
        jmp     .return

 %endif ; TMPL_RM

CPU 286
.286plus:
        ;
        ; The 4th bit of the machine status word / CR0 indicates the precense
        ; of a 80387 or later co-processor (a 80287+80386 => ET=0).  486 and
        ; later should be hardcoding this to 1, according to the documentation
        ; (need to test on 486SX).  The initial idea here then would be to
        ; assume 386+ if ET=1.
        ;
        ; The second idea was to check whether any reserved bits are set,
        ; because the 286 here has bits 4 thru 15 all set.  Unfortunately, it
        ; turned out the 386SX and AMD 486DX-40 also sets bits 4 thru 15 when
        ; using SMSW.  So, nothing conclusive to distinguish 386 from 286, but
        ; we've probably got a safe 486+ detection here.
        ;
        ;; @todo check if LOADALL can set any of the reserved bits on a 286 or 386.
        smsw    ax
        test    ax, ~(X86_CR0_PE | X86_CR0_MP | X86_CR0_EM | X86_CR0_TS | X86_CR0_ET | X86_CR0_NE)
        jz      .486plus

        ;
        ; The 286 stores 0xff in the high byte of the SIDT and SGDT base
        ; address (since it only did 24-bit addressing and the top 8-bit was
        ; reserved for the 386).  ASSUMES low IDT (which is the case for BS3Kit).
        ;
        sidt    [xBP - xCB*4 - 20h]
        cmp     byte [xBP - xCB*4 - 20h + 2 + 3], 0ffh
        jne     .386plus

 %if 0
        ;
        ; Detect 80286 by checking whether the IOPL and NT bits of EFLAGS can be
        ; modified or not.  There are different accounts of these bits.  Dr.Dobb's
        ; (http://www.drdobbs.com/embedded-systems/processor-detection-schemes/184409011)
        ; say they are undefined on 286es and will always be zero.  Whereas Intel
        ; iAPX 286 Programmer's Reference Manual (both order #210498-001 and
        ; #210498-003) documents both IOPL and NT, but with comment 4 on page
        ; C-43 stating that they cannot be POPFed in real mode and will both
        ; remain 0.  This is different from the 386+, where the NT flag isn't
        ; privileged according to page 3-37 in #230985-003.  Later Intel docs
        ; (#235383-052US, page 4-192) documents real mode as taking both NT and
        ; IOPL from what POPF reads off the stack - which is the behavior
        ; observed a 386SX here.
        ;
        test    al, X86_CR0_PE          ; This flag test doesn't work in protected mode, ...
        jnz     .386plus                ; ... so ASSUME 386plus if in PE for now.

        pushf                           ; Save a copy of the original flags for restoring IF.
        pushf
        pop     ax
        xor     ax, X86_EFL_IOPL | X86_EFL_NT   ; Try modify IOPL and NT.
        and     ax, ~X86_EFL_IF                 ; Try clear IF.
        push    ax                      ; Load modified flags.
        popf
        pushf                           ; Get actual flags.
        pop     dx
        popf                            ; Restore IF, IOPL and NT.
        cmp     ax, dx
        je      .386plus                ; If any of the flags are set, we're on 386+.

        ; While we could in theory be in v8086 mode at this point and be fooled
        ; by a flaky POPF implementation, we assume this isn't the case in our
        ; execution environment.
 %endif
.is_286:
        mov     ax, BS3CPU_80286
        jmp     .return
%endif ; !TMPL_CMN_PAGING

CPU 386
.386plus:
.486plus:
        ;
        ; Check for CPUID and AC.  The former flag indicates CPUID support, the
        ; latter was introduced with the 486.
        ;
        mov     ebx, esp                ; Save esp.
        and     esp, 0fffch             ; Clear high word and don't trigger ACs.
        pushfd
        mov     eax, [esp]              ; eax = original EFLAGS.
        xor     dword [esp], X86_EFL_ID | X86_EFL_AC ; Flip the ID and AC flags.
        popfd                           ; Load modified flags.
        pushfd                          ; Save actual flags.
        xchg    eax, [esp]              ; Switch, so the stack has the original flags.
        xor     eax, [esp]              ; Calc changed flags.
        popf                            ; Restore EFLAGS.
        mov     esp, ebx                ; Restore possibly unaligned ESP.
        test    eax, X86_EFL_ID
        jnz     .have_cpuid             ; If ID changed, we've got CPUID.
        test    eax, X86_EFL_AC
        mov     xAX, BS3CPU_80486
        jnz     .return                 ; If AC changed, we've got a 486 without CPUID (or similar).
        mov     xAX, BS3CPU_80386
        jmp     .return

CPU 586
.have_cpuid:
        ;
        ; Do a very simple minded check here using the (standard) family field.
        ; While here, we also check for PAE.
        ;
        mov     eax, 1
        cpuid

        ; Calc the extended family and model values before we mess up EAX.
        mov     cl, ah
        and     cl, 0fh
        cmp     cl, 0fh
        jnz     .not_extended_family
        mov     ecx, eax
        shr     ecx, 20
        and     cl, 7fh
        add     cl, 0fh
.not_extended_family:                   ; cl = family
        mov     ch, al
        shr     ch, 4
        cmp     cl, 0fh
        jae     .extended_model
        cmp     cl, 06h                 ; actually only intel, but we'll let this slip for now.
        jne     .done_model
.extended_model:
        shr     eax, 12
        and     al, 0f0h
        or      ch, al
.done_model:                            ; ch = model

        ; Start assembling return flags, checking for PSE + PAE.
        mov     eax, X86_CPUID_FEATURE_EDX_PSE | X86_CPUID_FEATURE_EDX_PAE
        and     eax, edx
        mov     ah, al
        AssertCompile(X86_CPUID_FEATURE_EDX_PAE_BIT > BS3CPU_F_PAE_BIT - 8)  ; 6 vs 10-8=2
        and     al, X86_CPUID_FEATURE_EDX_PAE
        shr     al, X86_CPUID_FEATURE_EDX_PAE_BIT - (BS3CPU_F_PAE_BIT - 8)
        AssertCompile(X86_CPUID_FEATURE_EDX_PSE_BIT == BS3CPU_F_PSE_BIT - 8) ; 3 vs 11-8=3
        and     ah, X86_CPUID_FEATURE_EDX_PSE
        or      ah, al
        or      ah, (BS3CPU_F_CPUID >> 8)

        ; Add the CPU type based on the family and model values.
        cmp     cl, 6
        jne     .not_family_06h
        mov     al, BS3CPU_PPro
        cmp     ch, 1
        jbe     .return
        mov     al, BS3CPU_PProOrNewer
        jmp     .NewerThanPPro

.not_family_06h:
        mov     al, BS3CPU_PProOrNewer
        ja      .NewerThanPPro
        cmp     cl, 5
        mov     al, BS3CPU_Pentium
        je      .return
        cmp     cl, 4
        mov     al, BS3CPU_80486
        je      .return
        cmp     cl, 3
        mov     al, BS3CPU_80386
        je      .return

.NewerThanPPro:

        ; Check for extended leaves and long mode.
        push    xAX                     ; save PAE+PProOrNewer
        mov     eax, 0x80000000
        cpuid
        sub     eax, 0x80000001         ; Minimum leaf 0x80000001
        cmp     eax, 0x00010000         ; At most 0x10000 leaves.
        ja      .no_ext_leaves

        mov     eax, 0x80000001
        cpuid
        pop     xAX                     ; restore PAE+PProOrNewer
        test    edx, X86_CPUID_EXT_FEATURE_EDX_LONG_MODE
        jz      .no_long_mode
        or      ah, ((BS3CPU_F_CPUID_EXT_LEAVES | BS3CPU_F_LONG_MODE) >> 8)
        jmp     .no_check_for_nx
.no_long_mode:
        or      ah, (BS3CPU_F_CPUID_EXT_LEAVES >> 8)
.no_check_for_nx:
        test    edx, X86_CPUID_EXT_FEATURE_EDX_NX
        jz      .return
        or      ax, BS3CPU_F_NX
        jmp     .return

.no_ext_leaves:
        pop     xAX                     ; restore PAE+PProOrNewer

CPU 8086
.return:
        ;
        ; Save the return value.
        ;
        mov     [BS3_DATA16_WRT(g_uBs3CpuDetected)], ax

        ;
        ; Epilogue.
        ;
        add     xSP, 20h
        pop     xBX
        pop     xDX
        pop     xCX
        popf
        pop     xBP
        BS3_HYBRID_RET

BS3_PROC_END_MODE   Bs3CpuDetect


%if TMPL_MODE == BS3_MODE_RM
BS3_BEGIN_TEXT16_NEARSTUBS
BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_NEAR
        call far TMPL_FAR_NM(Bs3CpuDetect)
        ret
BS3_PROC_END_MODE   Bs3CpuDetect
%endif