]> 4ch.mooo.com Git - 16.git/blob - src/lib/doslib/hw/cpu/cpusse.c
added a bunch of things~ and midi stuff~
[16.git] / src / lib / doslib / hw / cpu / cpusse.c
1
2 #if defined(TARGET_WINDOWS) && TARGET_MSDOS == 16
3 /* Win16: We're probably on a 386, but we could be on a 286 if Windows 3.1 is in standard mode.
4  *        If the user manages to run us under Windows 3.0, we could also run in 8086 real mode.
5  *        We still do the tests so the Windows API cannot deceive us, but we still need GetWinFlags
6  *        to tell between 8086 real mode + virtual8086 mode and protected mode. */
7 # include <windows.h>
8 # include <toolhelp.h>
9 #endif
10
11 #include <stdio.h>
12 #include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <assert.h>
17 #include <fcntl.h>
18 #include <dos.h>
19
20 #include <hw/cpu/cpu.h>
21 #include <hw/dos/dos.h>
22 #include <hw/dos/doswin.h>
23 #include <hw/cpu/cpusse.h>
24
25 /* DEBUG: Flush out calls that aren't there */
26 #ifdef TARGET_OS2
27 # define int86 ___EVIL___
28 # define ntvdm_RegisterModule ___EVIL___
29 # define ntvdm_UnregisterModule ___EVIL___
30 # define _dos_getvect ___EVIL___
31 # define _dos_setvect ___EVIL___
32 #endif
33
34 #if defined(TARGET_WINDOWS) && TARGET_MSDOS == 16
35 # include <hw/dos/winfcon.h>
36 #endif
37
38 unsigned char                   cpu_sse_usable = 0;
39 unsigned char                   cpu_sse_usable_probed = 0;
40 unsigned char                   cpu_sse_usable_can_turn_on = 0;
41 unsigned char                   cpu_sse_usable_can_turn_off = 0;
42 const char*                     cpu_sse_unusable_reason = NULL;
43 const char*                     cpu_sse_usable_detection_method = NULL;
44
45 #if !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
46 static unsigned char faulted = 0;
47
48 static void __declspec(naked) fault_int6_vec() {
49         /* the test routine executes a XORPS xmm0,xmm0 (3 bytes long).
50          * if we just IRET the CPU will go right back and execute it again */
51 # if TARGET_MSDOS == 32
52         __asm {
53                 push    ds
54                 push    eax
55                 mov     ax,seg faulted
56                 mov     ds,ax
57                 pop     eax
58                 mov     faulted,1
59                 pop     ds
60                 add     dword ptr [esp],3
61                 iretd
62         }
63 # else
64         __asm {
65                 push    bp
66                 mov     bp,sp
67                 add     word ptr [bp+2],3
68                 push    ds
69                 mov     bp,seg faulted
70                 mov     ds,bp
71                 mov     faulted,1
72                 pop     ds
73                 pop     bp
74                 iretf
75         }
76 # endif
77 }
78
79 # if TARGET_MSDOS == 16 && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
80 unsigned int _cdecl cpu_dpmi_win9x_sse_test();
81 # endif
82
83 # if TARGET_MSDOS == 32 && !defined(TARGET_OS2)
84 static void __declspec(naked) fault_int6() { /* DPMI exception handler */
85         __asm {
86                 .386p
87                 push            ds
88                 push            eax
89                 mov             ax,seg faulted
90                 mov             ds,ax
91                 mov             faulted,1
92                 add             dword ptr [esp+8+12],3 /* +3 bytes for 'xorps xmm0,xmm0' */
93                 pop             eax
94                 pop             ds
95                 retf
96         }
97 }
98 # endif
99 #elif defined(TARGET_WINDOWS) && TARGET_MSDOS == 16 && !defined(TARGET_OS2)
100 static unsigned char faulted = 0;
101
102 /* SS:SP + 12h = SS (fault)
103  * SS:SS + 10h = SP (fault)
104  * SS:SP + 0Eh = FLAGS (fault)
105  * SS:SP + 0Ch = CS (fault)
106  * SS:SP + 0Ah = IP (fault)
107  * SS:SP + 08h = handle (internal)
108  * SS:SP + 06h = interrupt number
109  * SS:SP + 04h = AX (to load into DS)
110  * SS:SP + 02h = CS (toolhelp.dll)
111  * SS:SP + 00h = IP (toolhelp.dll)
112  *
113  * to pass exception on: RETF
114  * to restart instruction: clear first 10 bytes of the stack, and IRET (??) */
115 static void __declspec(naked) fault_int6_toolhelp() {
116         __asm {
117                 .386p
118                 push            ds
119                 push            ax
120                 push            bp
121                 mov             bp,sp
122
123                 /* is this for INT 6h? */
124                 cmp             word ptr [bp+6+6],6     /* SS:SP + 06h = interrupt number */
125                 jnz             pass_on
126
127                 /* set the faulted flag, change the return address, then IRET directly back */
128                 mov             ax,seg faulted
129                 mov             ds,ax
130                 mov             faulted,1
131                 add             word ptr [bp+6+10],3
132                 pop             bp
133                 pop             ax
134                 pop             ds
135                 add             sp,0Ah                  /* throw away handle, int number, and CS:IP return */
136                 iret
137
138 /* tell ToolHelp we didn't handle the interrupt */
139 pass_on:        retf
140         }
141 }
142
143 static void __declspec(naked) fault_int6() { /* DPMI exception handler */
144         __asm {
145                 .386p
146                 push            ds
147                 push            ax
148                 push            bp
149                 mov             bp,sp
150                 mov             ax,seg faulted
151                 mov             ds,ax
152                 mov             faulted,1
153                 add             word ptr [bp+6+6],3
154                 pop             bp
155                 pop             ax
156                 pop             ds
157                 retf
158         }
159 }
160 #endif
161
162 /* check if SSE is usable. Just because CPUID says it's there
163  * doesn't mean the OS enabled it.
164  *
165  * if do_enable is nonzero and the function detects that it's
166  * possible, the function will enable SSE and return success */
167 int cpu_check_sse_is_usable() {
168         if (!cpu_sse_usable_probed) {
169                 cpu_sse_usable_probed = 1;
170                 cpu_sse_usable = 0;
171                 cpu_sse_unusable_reason = "";
172                 cpu_sse_usable_detection_method = "None";
173                 cpu_sse_usable_can_turn_off = 0;
174                 cpu_sse_usable_can_turn_on = 0;
175
176                 if (cpu_basic_level < 0) cpu_probe();
177
178                 if (!(cpu_flags & CPU_FLAG_CPUID)) {
179                         cpu_sse_unusable_reason = "No CPUID available";
180                         return 0;
181                 }
182                 if (!(cpu_cpuid_features.a.raw[2] & (1UL << 25UL))) {
183                         cpu_sse_unusable_reason = "CPUID indicates SSE is not present";
184                         return 0;
185                 }
186
187 #ifdef TARGET_WINDOWS
188 /* ==================== RUNNING WITHIN WINDOWS =================== */
189 /* Within Windows, we have no way to enable SSE if the OS kernel doesn't support it.
190  * So we first must learn whether or not the OS supports it.
191  *    Guide: Windows NT/2000/XP/Vista/etc... you can use IsProcessorFeaturePresent()
192  *           Windows 95/98/ME... there is no way other than attempting the instruction to see if it causes a fault.
193  *           Windows 3.1/3.0... NO. And you cannot enable it either! Buuuuut.... if the Windows 3.1 binary is run
194  *           under Windows XP/ME/98 and the kernel has SSE enabled, then it is possible to use SSE instructions.
195  *
196  *    NOTE: Any Windows 9x kernel prior to 98SE does not support SSE, but if something happens to enable it in CR4
197  *          prior to Windows taking control, then it will stay enabled while Windows is running. Those versions
198  *          treat the SSE enable bit as unknown and they don't change it. BUT also realize that the SSE registers
199  *          are not saved and restored by the kernel scheduler! If you are the only process that will be using SSE
200  *          that is fine, but if two or more tasks try to use SSE there will be serious conflicts and possibly
201  *          a crash.
202  *
203  * Reading CR4 to determine availability has the same effects as it does for the MS-DOS code, either
204  * nonsense values (Windows 9x/ME) or it causes a fault (Windows NT). */
205                 detect_windows();
206 # if TARGET_MSDOS == 32
207 #  ifdef WIN386
208                 /* Windows 3.0/3.1 Win386: the underlying system is 16-bit and we're 32-bit through an extender */
209                 cpu_sse_unusable_reason = "SSE support for Windows 3.x Win386 is not implemented";
210                 return 0;
211 #  else
212                 {
213                         BOOL (WINAPI *__IsProcFeaturePresent)(DWORD f) = NULL;
214
215                         if (windows_mode == WINDOWS_NT)
216                                 __IsProcFeaturePresent = (BOOL (WINAPI *)(DWORD))GetProcAddress(GetModuleHandle("KERNEL32.DLL"),"IsProcessorFeaturePresent");
217
218                         if (__IsProcFeaturePresent != NULL) {
219                                 cpu_sse_usable_detection_method = "IsProcessorFeaturePresent [WinNT]";
220                                 printf("Using IsProcessorFeaturePresent\n");
221                                 if (!__IsProcFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE))
222                                         cpu_sse_unusable_reason = "Windows NT HAL says SSE not enabled";
223                                 else
224                                         cpu_sse_usable = 1;
225                         }
226                         else {
227                                 BYTE ok=1;
228
229                                 cpu_sse_usable_detection_method = "Executing SSE to see if it causes a fault [Win31s/9x/ME/NT]";
230
231                                 /* we just have to try */
232                                 __try {
233                                         __asm {
234                                                 .686p
235                                                         .xmm
236                                                         xorps   xmm0, xmm0
237                                         }
238                                 }
239                                 __except (EXCEPTION_EXECUTE_HANDLER) {
240                                         ok = 0;
241                                 }
242
243                                 if (!ok) {
244                                         cpu_sse_unusable_reason = "Windows 3.1/9x/ME kernel does not have SSE enabled";
245                                         return 0;
246                                 }
247                                 else {
248                                         cpu_sse_usable = 1;
249                                 }
250                         }
251                 }
252 #  endif
253 # else /* TARGET_MSDOS == 16 */
254                 if ((windows_mode == WINDOWS_STANDARD || windows_mode == WINDOWS_ENHANCED) && windows_version < 0x35F) {
255                         /* Windows 3.0/3.1: We can abuse the DPMI server to hook INT 6h exceptions.
256                          * Very clean, and it does not require TOOLHELP.DLL. But it doesn't work under Windows 9x/ME.
257                          * The DPMI call silently fails and KERNEL32.DLL catches the fault without passing it on to us. */
258                         void far *op;
259
260                         op = win16_getexhandler(6);
261                         cpu_sse_usable_detection_method = "Hooking INT 6, executing SSE to see if it causes a fault [Win16]";
262                         if (win16_setexhandler(6,fault_int6)) {
263                                 faulted = 0;
264                                 __asm {
265                                         .686p
266                                         .xmm
267                                         xorps   xmm0, xmm0
268                                 }
269                                 win16_setexhandler(6,op);
270                         }
271                         else {
272                                 win16_setexhandler(6,op);
273                                 cpu_sse_unusable_reason = "Unable to hook INT 6 by DPMI";
274                                 return 0;
275                         }
276
277                         if (faulted) {
278                                 cpu_sse_unusable_reason = "Windows 3.x kernel does not have SSE enabled";
279                                 return 0;
280                         }
281                         else {
282                                 cpu_sse_usable = 1;
283                         }
284                 }
285                 /* Windows 9x/ME. Abusing DPMI as in Windows 3.1 doesn't work, the calls silently fail. But Microsoft
286                  * apparently made sure the TOOLHELP API functions do their job, so we use that technique. It even works
287                  * under NTVDM.EXE in Windows NT/2000/XP */
288                 else if ((windows_mode == WINDOWS_STANDARD || windows_mode == WINDOWS_ENHANCED || windows_mode == WINDOWS_NT) && ToolHelpInit()) {
289                         cpu_sse_usable_detection_method = "InterruptRegister/TOOLHELP.DLL, executing SSE to see if it causes a fault [Win16]";
290                         if (__InterruptRegister(NULL,MakeProcInstance((FARPROC)fault_int6_toolhelp,_win_hInstance))) {
291                                 faulted = 0;
292                                 __asm {
293                                         .686p
294                                         .xmm
295                                         xorps   xmm0, xmm0
296                                 }
297
298                                 if (!__InterruptUnRegister(NULL))
299                                         MessageBox(NULL,"WARNING: Unable to unregister interrupt","",MB_OK);
300
301                                 if (faulted) {
302                                         cpu_sse_unusable_reason = "Windows 9x/ME/NT kernel does not have SSE enabled";
303                                         return 0;
304                                 }
305                                 else {
306                                         cpu_sse_usable = 1;
307                                 }
308                         }
309                         else {
310                                 MessageBox(NULL,"WARNING: Unable to register interrupt","",MB_OK);
311                         }
312                 }
313                 else {
314                         cpu_sse_unusable_reason = "This library does not have support for detecting SSE from Win16 under Windows 3.0 Real Mode";
315                         return 0;
316                 }
317 # endif
318 #elif defined(TARGET_OS2)
319 /* ==================== RUNNING AS OS/2 APP ====================== */
320                 /* TODO */
321                 cpu_sse_unusable_reason = "Assuming not present";
322                 cpu_sse_usable = 0;
323                 cpu_sse_usable_detection_method = "None";
324                 cpu_sse_usable_can_turn_on = 0;
325                 cpu_sse_usable_can_turn_off = 0;
326 #else
327 /* ==================== RUNNING AS MS-DOS APP ==================== */
328                 detect_windows();
329
330                 if ((windows_mode == WINDOWS_STANDARD || windows_mode == WINDOWS_ENHANCED) && TARGET_MSDOS == 16) {
331 #if TARGET_MSDOS == 16
332                         /* Windows 9x/ME DOS box.
333                          * we can't read the control registers, and hooking INT 6 doesn't work. If the SSE test causes
334                          * an Invalid Opcode exception Windows 9x will NOT forward the exception down to us!
335                          * Same with Windows 3.0/3.1 Standard/Enhanced mode!
336                          *
337                          * But, Windows 9x does offer DPMI. So to carry out the test, we use DPMI to enter protected mode,
338                          * set up DPMI exception handlers, perform the test, and then use DPMI to exit back to real mode
339                          * with the results.
340                          *
341                          * Note that this code is not necessary for Windows NT/2000/XP/Vista/etc. as NTVDM.EXE contains
342                          * code to forward the Invalid Opcode exception to us if it sees that we hooked it */
343                         cpu_sse_usable_detection_method = "Hook INT 6h, execute SSE to see if it causes a fault [DPMI under Win 3.1/9x/ME DOS box]";
344                         probe_dpmi();
345                         if (dpmi_present) {
346                                 /* to carry out this test, we must enter protected mode through DPMI. we must initialize
347                                  * DPMI if not already done, or if the caller has already gone through DPMI, we must
348                                  * enter using whatever bit width he chose.
349                                  *
350                                  * NOTE: This means then, that if the calling program needs DPMI the program must have
351                                  *       set it's own preferences up prior to calling this function, because otherwise
352                                  *       you're going to be stuck with our preferences. Now maybe if DPMI had thought
353                                  *       about something like... I dunno... a way to un-initialize DPMI for itself,
354                                  *       maybe we wouldn't have this problem, would we? */
355                                 if (dpmi_entered == 0)
356                                         dpmi_enter(DPMI_ENTER_AUTO);
357
358                                 if (dpmi_entered != 0) {
359                                         unsigned int reason = cpu_dpmi_win9x_sse_test();
360                                         if (reason == 0) {
361                                                 /* it worked */
362                                                 cpu_sse_usable = 1;
363                                         }
364                                         else if (reason == 1) {
365                                                 /* test OK, sse not available */
366                                                 cpu_sse_unusable_reason = "SSE is currently disabled, nobody has enabled it yet";
367                                         }
368                                 }
369                                 else {
370                                         cpu_sse_unusable_reason = "Unable to enter DPMI protected mode";
371                                 }
372                         }
373                         else {
374                                 cpu_sse_unusable_reason = "As an MS-DOS application I have no way to test for SSE from within Win 3.1/9x/ME DOS box, DPMI is not available";
375                         }
376 #endif
377                 }
378                 else if (cpu_v86_active || windows_mode == WINDOWS_NT ||
379                         ((windows_mode == WINDOWS_STANDARD || windows_mode == WINDOWS_ENHANCED) && TARGET_MSDOS == 32)) {
380                         /* pure DOS mode with virtual 8086 mode, or Windows NT DOS box.
381                          * we can't read the control registers (or under EMM386.EXE we can, but within v86 mode it's
382                          * not wise to assume that we can). Note that DOS32a is able to catch Invalid Opcode exceptions,
383                          * even from within Windows 9x/ME/NT, so when compiled as a 32-bit DOS app we also need to use DPMI functions
384                          * "set exception handler" to ensure that we catch the fault. */
385                         void far *oh;
386 #if TARGET_MSDOS == 32
387                         void far *oh_ex;
388 #endif
389
390                         cpu_sse_usable_detection_method = "Hook INT 6h, execute SSE to see if it causes a fault [MS-DOS]";
391
392 #if TARGET_MSDOS == 32
393                         oh_ex = dpmi_getexhandler(6);
394                         dpmi_setexhandler(6,(void far*)fault_int6);
395 #endif
396
397                         oh = _dos_getvect(6);
398                         _dos_setvect(6,(void far*)fault_int6_vec);
399
400                         __asm {
401                                 .686p
402                                 .xmm
403                                 xorps   xmm0, xmm0
404                         }
405
406                         _dos_setvect(6,oh);
407 #if TARGET_MSDOS == 32
408                         dpmi_setexhandler(6,oh_ex);
409 #endif
410
411                         /* TODO: If we're in pure DOS mode, and virtual 8086 mode is active, and we know VCPI is present,
412                          *       then it is possible for us to enable/disable SSE by switching into protected mode via VCPI */
413
414                         if (faulted) {
415                                 cpu_sse_unusable_reason = "SSE is currently disabled, nobody has enabled it yet";
416                         }
417                         else {
418                                 cpu_sse_usable = 1;
419                         }
420                 }
421                 else {
422                         /* pure DOS mode. we can read the control registers without crashing */
423                         uint32_t v=0;
424
425                         cpu_sse_usable_detection_method = "80386 MOV CR4 [MS-DOS]";
426
427                         __asm {
428                                 .386p
429                                 mov     eax,cr4
430                                 mov     v,eax
431                         }
432
433                         if (v & 0x200) {
434                                 cpu_sse_usable = 1;
435                         }
436                         else {
437                                 cpu_sse_unusable_reason = "SSE is currently disabled";
438                         }
439
440                         cpu_sse_usable_can_turn_on = 1;
441                         cpu_sse_usable_can_turn_off = 1;
442                 }
443 #endif /* TARGET_WINDOWS */
444         }
445
446         return cpu_sse_usable;
447 }
448
449 int cpu_sse_disable() {
450 #if !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
451         uint32_t confidence=0;
452 #endif
453
454         if (!(cpu_flags & CPU_FLAG_CPUID))
455                 return 0;
456         if (!(cpu_cpuid_features.a.raw[2] & (1UL << 25UL)))
457                 return 0;
458         if (!cpu_sse_usable_can_turn_off)
459                 return 0;
460
461 #if defined(TARGET_WINDOWS) || defined(TARGET_OS2)
462         return 0; /* it's very unlikely we could ever touch the control registers from within Windows */
463         /* FIXME: Maybe as a Win32 app under Windows 9x we could try? */
464 #else
465         __asm {
466                 .386p
467                 mov     eax,cr4
468                 and     eax,0xFFFFFDFF  /* bit 9 */
469                 mov     cr4,eax
470                 mov     ebx,eax         /* remember what we wrote */
471
472                 mov     eax,cr4
473                 and     eax,0x200
474                 and     ebx,0x200
475                 xor     eax,ebx
476                 mov     confidence,eax  /* EAX = nonzero if write didn't work */
477         }
478
479         if (confidence) {
480                 cpu_sse_unusable_reason = "Attempting to write CR4 failed";
481                 return 0;
482         }
483
484         cpu_sse_usable = 0;
485         return 1;
486 #endif
487 }
488
489 int cpu_sse_enable() {
490 #if !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
491         uint32_t confidence=0;
492 #endif
493
494         if (!(cpu_flags & CPU_FLAG_CPUID))
495                 return 0;
496         if (!(cpu_cpuid_features.a.raw[2] & (1UL << 25UL)))
497                 return 0;
498         if (!cpu_sse_usable_can_turn_on)
499                 return 0;
500
501 #if defined(TARGET_WINDOWS) || defined(TARGET_OS2)
502         return 0; /* it's very unlikely we could ever touch the control registers from within Windows */
503         /* FIXME: Maybe as a Win32 app under Windows 9x we could try? */
504 #else
505         __asm {
506                 .386p
507                 mov     eax,cr4
508                 or      eax,0x200       /* bit 9 */
509                 mov     cr4,eax
510                 mov     ebx,eax         /* remember what we wrote */
511
512                 mov     eax,cr4
513                 and     eax,0x200
514                 and     ebx,0x200
515                 xor     eax,ebx
516                 mov     confidence,eax  /* EAX = nonzero if write didn't work */
517         }
518
519         if (confidence) {
520                 cpu_sse_unusable_reason = "Attempting to write CR4 failed";
521                 return 0;
522         }
523
524         cpu_sse_usable = 1;
525         return 1;
526 #endif
527 }
528