/
cpu.cpp
3414 lines (3072 loc) · 102 KB
/
cpu.cpp
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) 2002-2015 The DOSBox Team
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <assert.h>
#include <sstream>
#include <stddef.h>
#include "dosbox.h"
#include "cpu.h"
#include "memory.h"
#include "debug.h"
#include "mapper.h"
#include "setup.h"
#include "programs.h"
#include "paging.h"
#include "callback.h"
#include "lazyflags.h"
#include "support.h"
#include "control.h"
/* caution: do not uncomment unless you want a lot of spew */
//#define CPU_DEBUG_SPEW
#if defined(CPU_DEBUG_SPEW)
# define _LOG LOG
#else
class _LOG : public LOG { // HACK
public:
_LOG(LOG_TYPES type , LOG_SEVERITIES severity) : LOG(type,severity) { }
};
# undef LOG
# if defined (_MSC_VER)
# define LOG(X,Y)
# else
# define LOG(X,Y) CPU_LOG
# define CPU_LOG(...)
# endif
#endif
bool enable_weitek = false;
bool CPU_NMI_gate = true;
bool CPU_NMI_active = false;
bool CPU_NMI_pending = false;
bool do_seg_limits = false;
bool enable_fpu = true;
bool enable_msr = true;
bool enable_cmpxchg8b = true;
bool ignore_undefined_msr = true;
extern bool ignore_opcode_63;
bool cpu_double_fault_enable;
bool cpu_triple_fault_reset;
int cpu_rep_max = 0;
Bitu DEBUG_EnableDebugger(void);
extern void GFX_SetTitle(Bit32s cycles, Bits frameskip, Bits timing, bool paused);
CPU_Regs cpu_regs;
CPUBlock cpu;
Segments Segs;
/* [cpu] setting realbig16.
* If set, allow code to switch back to real mode with the B (big) set in the
* code selector, and retain the state of the B bit while running in 16-bit
* real mode. Needed for demos like Project Angel.
*
* Modifications are a derivative of this patch:
*
* cpu.diff from http://www.vogons.org/viewtopic.php?f=33&t=28226&start=5
*
* The main difference between that patch and the modifications derived is that
* I modified additional points to keep the big bit set (the original cpu.diff
* missed the CALL and JMP emulation that also reset the flag)
*
* It's well known that DOS programs can access all 4GB of addressable memory by
* jumping into protected mode, loading segment registers with a 4GB limit, then
* jumping back to real mode without reloading the segment registers, knowing
* that Intel processors will not update the shadow part of the segment register
* in real mode. I'm guessing that what Project Angel is doing, is using the same
* abuse of protected mode to also set the B (big) bit in the code segment so that
* it's code segment can extend past 64KB (huge unreal mode), which works until
* something like an interrupt chops off the top 16 bits of the instruction pointer.
*
* I want to clarify that realbig16 is an OPTION that is off by default, because
* I am uncertain at this time whether or not the patch breaks any DOS games or
* OS emulation. It is rare for a DOS game or demo to actually abuse the CPU in
* that way, so it is set up that you have to enable it if you need it. --J.C.
*
* J.C. TODO: Write a program that abuses the B (big) bit in real mode in the same
* way that Project Angel supposedly does, see if it works, then test it
* and Project Angel on some old 386/486/Pentium systems lying around to
* see how compatible such abuse is with PC hardware. That would make a
* good Hackipedia.org episode as well. --J.C.
*
* 2014/01/19: I can attest that this patch does indeed allow Project Angel to
* run when realbig16=true. And if GUS emulation is active, there is
* music as well. Now as for reliability... testing shows that one of
* three things can happen when you run the demo:
*
* 1) the demo hangs on startup, either right away or after it starts
* the ominous music (if you sit for 30 seconds waiting for the
* music to build up and nothing happens, consider closing the
* emulator and trying again).
*
* 2) the demo runs perfectly fine, but timing is slightly screwed up,
* and parts of the music sound badly out of sync with each other,
* or randomly slows to about 1/2 speed in some sections, animations
* run slow sometimes. If this happens, make sure you didn't set
* forcerate=ntsc.
*
* 3) the demo runs perfectly fine, with no timing issues, except that
* DOSBox's S3 emulation is not quite on-time and the bottom 1/4th
* of the screen flickers with the contents of the next frame that
* the demo is still drawing :(
*
* --J.C. */
bool cpu_allow_big16 = false;
Bit32s CPU_Cycles = 0;
Bit32s CPU_CycleLeft = 3000;
Bit32s CPU_CycleMax = 3000;
Bit32s CPU_OldCycleMax = 3000;
Bit32s CPU_CyclePercUsed = 100;
Bit32s CPU_CycleLimit = -1;
Bit32s CPU_CycleUp = 0;
Bit32s CPU_CycleDown = 0;
Bit32s CPU_CyclesSet = 3000;
Bit64s CPU_IODelayRemoved = 0;
char core_mode[16];
CPU_Decoder * cpudecoder;
bool CPU_CycleAutoAdjust = false;
bool CPU_SkipCycleAutoAdjust = false;
Bitu CPU_AutoDetermineMode = 0;
Bitu CPU_ArchitectureType = CPU_ARCHTYPE_MIXED;
Bitu CPU_extflags_toggle=0; // ID and AC flags may be toggled depending on emulated CPU architecture
Bitu CPU_PrefetchQueueSize=0;
void CPU_Core_Full_Init(void);
void CPU_Core_Normal_Init(void);
void CPU_Core_Simple_Init(void);
#if (C_DYNAMIC_X86)
void CPU_Core_Dyn_X86_Init(void);
void CPU_Core_Dyn_X86_Cache_Init(bool enable_cache);
void CPU_Core_Dyn_X86_Cache_Close(void);
void CPU_Core_Dyn_X86_SetFPUMode(bool dh_fpu);
void CPU_Core_Dyn_X86_Cache_Reset(void);
#endif
/* called to signal an NMI. */
/* NTS: From the Intel 80386 programmer's reference manual:
*
* "
* 9.2.1 NMI Masks Further NMIs
*
* While an NMI handler is executing, the processor ignores further interrupt
* signals at the NMI pin until the next IRET instruction is executed.
* "
*
* This is why, further down, CPU_IRET() clears the CPU_NMI_active flag.
*
*
* And, in response to my father's incredulous response regarding the fact that
* NMI is edge-triggered (from the Intel 386SX Microprocessor datasheet):
*
* "
* Non-Maskable Interrupt Request (NMI))
*
* This input indicates a request for interrupt service
* which cannot be masked by software. The non-
* maskable interrupt request is always processed ac-
* cording to the pointer or gate in slot 2 of the interrupt
* table. Because of the fixed NMI slot assignment, no
* interrupt acknowledge cycles are performed when
* processing NMI.
*
* NMI is an active HIGH, rising edge-sensitive asyn-
* chronous signal. Setup and hold times, t27 and and t28,
* relative to the CLK2 signal must be met to guarantee
* recognition at a particular clock edge. To assure rec-
* ognition of NMI, it must be inactive for at least eight
* CLK2 periods, and then be active for at least eight
* CLK2 periods before the beginning of the instruction
* boundary in the Intel386 SX Microprocessor's Exe-
* cution Unit.
*
* Once NMI processing has begun, no additional
* NMI's are processed until after the next IRET in-
* struction, which is typically the end of the NMI serv-
* ice routine. If NMI is re-asserted prior to that time,
* however, one rising edge on NMI will be remem-
* bered for processing after executing the next IRET
* instruction
* "
*
* From the Pentium Pro Processor datasheet:
*
* "
* A.38 NMI (I)
*
* The NMI signal is the Non-maskable Interrupt signal.
* It is the state of the LINT1 signal when APIC is
* disabled. Asserting NMI causes an interrupt with an
* internally supplied vector value of 2. An external
* interrupt-acknowledge transaction is not generated. If
* NMI is asserted during the execution of an NMI
* service routine, it remains pending and is recognized
* after the IRET is executed by the NMI service
* routine. At most, one assertion of NMI is held
* pending.
*
* NMI is rising-edge sensitive. Recognition of NMI is
* guaranteed in a specific clock if it is asserted
* synchronously and meets the setup and hold times. If
* asserted asynchronously, active and inactive pulse
* widths must be a minimum of two clocks. In FRC
* mode, NMI must be synchronous to BCLK.
* "
*
* Similar references exist in the Pentium III and Pentium 4
* datasheets, while later on in the Core 2 datasheets there
* is no mention whatsoever to the NMI that I can find.
*/
void CPU_NMI_Interrupt() {
if (CPU_NMI_active) E_Exit("CPU_NMI_Interrupt() called while NMI already active");
CPU_NMI_active = true;
CPU_NMI_pending = false;
CPU_Interrupt(2/*INT 2 = NMI*/,0,reg_eip);
}
void CPU_Raise_NMI() {
CPU_NMI_pending = true;
if (!CPU_NMI_active && CPU_NMI_gate) CPU_NMI_Interrupt();
}
void CPU_Check_NMI() {
if (!CPU_NMI_active && CPU_NMI_gate && CPU_NMI_pending) CPU_NMI_Interrupt();
}
/* In debug mode exceptions are tested and dosbox exits when
* a unhandled exception state is detected.
* USE CHECK_EXCEPT to raise an exception in that case to see if that exception
* solves the problem.
*
* In non-debug mode dosbox doesn't do detection (and hence doesn't crash at
* that point). (game might crash later due to the unhandled exception) */
#define CPU_CHECK_EXCEPT 1
// #define CPU_CHECK_IGNORE 1
#if C_DEBUG
// #define CPU_CHECK_EXCEPT 1
// #define CPU_CHECK_IGNORE 1
/* Use CHECK_EXCEPT when something doesn't work to see if a exception is
* needed that isn't enabled by default.*/
#else
/* NORMAL NO CHECKING => More Speed */
//#define CPU_CHECK_IGNORE 1
#endif /* C_DEBUG */
#if defined(CPU_CHECK_IGNORE)
#define CPU_CHECK_COND(cond,msg,exc,sel) { \
if (cond) do {} while (0); \
}
#elif defined(CPU_CHECK_EXCEPT)
#define CPU_CHECK_COND(cond,msg,exc,sel) { \
if (cond) { \
CPU_Exception(exc,sel); \
return; \
} \
}
#else
#define CPU_CHECK_COND(cond,msg,exc,sel) { \
if (cond) E_Exit(msg); \
}
#endif
void Descriptor::Load(PhysPt address) {
cpu.mpl=0;
Bit32u* data = (Bit32u*)&saved;
*data = mem_readd(address);
*(data+1) = mem_readd(address+4);
cpu.mpl=3;
}
void Descriptor:: Save(PhysPt address) {
cpu.mpl=0;
Bit32u* data = (Bit32u*)&saved;
mem_writed(address,*data);
mem_writed(address+4,*(data+1));
cpu.mpl=3;
}
void CPU_Push16(Bitu value) {
Bit32u new_esp=(reg_esp&cpu.stack.notmask)|((reg_esp-2)&cpu.stack.mask);
mem_writew(SegPhys(ss) + (new_esp & cpu.stack.mask) ,value);
reg_esp=new_esp;
}
void CPU_Push32(Bitu value) {
Bit32u new_esp=(reg_esp&cpu.stack.notmask)|((reg_esp-4)&cpu.stack.mask);
mem_writed(SegPhys(ss) + (new_esp & cpu.stack.mask) ,value);
reg_esp=new_esp;
}
Bitu CPU_Pop16(void) {
Bitu val=mem_readw(SegPhys(ss) + (reg_esp & cpu.stack.mask));
reg_esp=(reg_esp&cpu.stack.notmask)|((reg_esp+2)&cpu.stack.mask);
return val;
}
Bitu CPU_Pop32(void) {
Bitu val=mem_readd(SegPhys(ss) + (reg_esp & cpu.stack.mask));
reg_esp=(reg_esp&cpu.stack.notmask)|((reg_esp+4)&cpu.stack.mask);
return val;
}
PhysPt SelBase(Bitu sel) {
if (cpu.cr0 & CR0_PROTECTION) {
Descriptor desc;
cpu.gdt.GetDescriptor(sel,desc);
return desc.GetBase();
} else {
return sel<<4;
}
}
void CPU_SetCPL(Bitu newcpl) {
if (newcpl != cpu.cpl) {
if (paging.enabled) {
if ( ((cpu.cpl < 3) && (newcpl == 3)) || ((cpu.cpl == 3) && (newcpl < 3)) )
PAGING_SwitchCPL(newcpl == 3);
}
cpu.cpl = newcpl;
}
}
void CPU_SetFlags(Bitu word,Bitu mask) {
/* 8086/286 flags manipulation.
* For more information read about the Intel CPU detection algorithm and other bits of info:
* [http://www.rcollins.org/ddj/Sep96/Sep96.html] */
/* 8086: bits 12-15 cannot be zeroed */
if (CPU_ArchitectureType <= CPU_ARCHTYPE_80186) {
/* update mask and word to ensure bits 12-15 are set */
word |= 0xF000;
mask |= 0xF000;
}
/* 286 real mode: bits 12-15 bits cannot be set, always zero */
else if (CPU_ArchitectureType <= CPU_ARCHTYPE_286) {
if (!(cpu.cr0 & CR0_PROTECTION)) {
/* update mask and word to ensure bits 12-15 are zero */
word &= ~0xF000;
mask |= 0xF000;
}
}
else {
mask |= CPU_extflags_toggle; // ID-flag and AC-flag can be toggled on CPUID-supporting CPUs
}
reg_flags=(reg_flags & ~mask)|(word & mask)|2;
cpu.direction=1-((reg_flags & FLAG_DF) >> 9);
// ^ NTS: Notice the DF flag is bit 10. This code computes (reg_flags & FLAG_DF) >> 9 on purpose.
// It's not a typo (9 vs 10), it's done to set cpu.direction to either 1 or -1.
}
bool CPU_PrepareException(Bitu which,Bitu error) {
cpu.exception.which=which;
cpu.exception.error=error;
return true;
}
bool CPU_CLI(void) {
if (cpu.pmode && ((!GETFLAG(VM) && (GETFLAG_IOPL<cpu.cpl)) || (GETFLAG(VM) && (GETFLAG_IOPL<3)))) {
return CPU_PrepareException(EXCEPTION_GP,0);
} else {
SETFLAGBIT(IF,false);
return false;
}
}
bool CPU_STI(void) {
if (cpu.pmode && ((!GETFLAG(VM) && (GETFLAG_IOPL<cpu.cpl)) || (GETFLAG(VM) && (GETFLAG_IOPL<3)))) {
return CPU_PrepareException(EXCEPTION_GP,0);
} else {
SETFLAGBIT(IF,true);
return false;
}
}
bool CPU_POPF(Bitu use32) {
if (cpu.pmode && GETFLAG(VM) && (GETFLAG(IOPL)!=FLAG_IOPL)) {
/* Not enough privileges to execute POPF */
return CPU_PrepareException(EXCEPTION_GP,0);
}
Bitu mask=FMASK_ALL;
/* IOPL field can only be modified when CPL=0 or in real mode: */
if (cpu.pmode && (cpu.cpl>0)) mask &= (~FLAG_IOPL);
if (cpu.pmode && !GETFLAG(VM) && (GETFLAG_IOPL<cpu.cpl)) mask &= (~FLAG_IF);
if (use32)
CPU_SetFlags(CPU_Pop32(),mask);
else CPU_SetFlags(CPU_Pop16(),mask & 0xffff);
DestroyConditionFlags();
return false;
}
bool CPU_PUSHF(Bitu use32) {
if (cpu.pmode && GETFLAG(VM) && (GETFLAG(IOPL)!=FLAG_IOPL)) {
/* Not enough privileges to execute PUSHF */
return CPU_PrepareException(EXCEPTION_GP,0);
}
FillFlags();
if (use32)
CPU_Push32(reg_flags & 0xfcffff);
else CPU_Push16(reg_flags);
return false;
}
void CPU_CheckSegments(void) {
bool needs_invalidation=false;
Descriptor desc;
if (!cpu.gdt.GetDescriptor(SegValue(es),desc)) needs_invalidation=true;
else switch (desc.Type()) {
case DESC_DATA_EU_RO_NA: case DESC_DATA_EU_RO_A: case DESC_DATA_EU_RW_NA: case DESC_DATA_EU_RW_A:
case DESC_DATA_ED_RO_NA: case DESC_DATA_ED_RO_A: case DESC_DATA_ED_RW_NA: case DESC_DATA_ED_RW_A:
case DESC_CODE_N_NC_A: case DESC_CODE_N_NC_NA: case DESC_CODE_R_NC_A: case DESC_CODE_R_NC_NA:
if (cpu.cpl>desc.DPL()) needs_invalidation=true; break;
default: break; }
if (needs_invalidation) CPU_SetSegGeneral(es,0);
needs_invalidation=false;
if (!cpu.gdt.GetDescriptor(SegValue(ds),desc)) needs_invalidation=true;
else switch (desc.Type()) {
case DESC_DATA_EU_RO_NA: case DESC_DATA_EU_RO_A: case DESC_DATA_EU_RW_NA: case DESC_DATA_EU_RW_A:
case DESC_DATA_ED_RO_NA: case DESC_DATA_ED_RO_A: case DESC_DATA_ED_RW_NA: case DESC_DATA_ED_RW_A:
case DESC_CODE_N_NC_A: case DESC_CODE_N_NC_NA: case DESC_CODE_R_NC_A: case DESC_CODE_R_NC_NA:
if (cpu.cpl>desc.DPL()) needs_invalidation=true; break;
default: break; }
if (needs_invalidation) CPU_SetSegGeneral(ds,0);
needs_invalidation=false;
if (!cpu.gdt.GetDescriptor(SegValue(fs),desc)) needs_invalidation=true;
else switch (desc.Type()) {
case DESC_DATA_EU_RO_NA: case DESC_DATA_EU_RO_A: case DESC_DATA_EU_RW_NA: case DESC_DATA_EU_RW_A:
case DESC_DATA_ED_RO_NA: case DESC_DATA_ED_RO_A: case DESC_DATA_ED_RW_NA: case DESC_DATA_ED_RW_A:
case DESC_CODE_N_NC_A: case DESC_CODE_N_NC_NA: case DESC_CODE_R_NC_A: case DESC_CODE_R_NC_NA:
if (cpu.cpl>desc.DPL()) needs_invalidation=true; break;
default: break; }
if (needs_invalidation) CPU_SetSegGeneral(fs,0);
needs_invalidation=false;
if (!cpu.gdt.GetDescriptor(SegValue(gs),desc)) needs_invalidation=true;
else switch (desc.Type()) {
case DESC_DATA_EU_RO_NA: case DESC_DATA_EU_RO_A: case DESC_DATA_EU_RW_NA: case DESC_DATA_EU_RW_A:
case DESC_DATA_ED_RO_NA: case DESC_DATA_ED_RO_A: case DESC_DATA_ED_RW_NA: case DESC_DATA_ED_RW_A:
case DESC_CODE_N_NC_A: case DESC_CODE_N_NC_NA: case DESC_CODE_R_NC_A: case DESC_CODE_R_NC_NA:
if (cpu.cpl>desc.DPL()) needs_invalidation=true; break;
default: break; }
if (needs_invalidation) CPU_SetSegGeneral(gs,0);
}
class TaskStateSegment {
public:
TaskStateSegment() {
valid=false;
}
bool IsValid(void) {
return valid;
}
Bitu Get_back(void) {
cpu.mpl=0;
Bit16u backlink=mem_readw(base);
cpu.mpl=3;
return backlink;
}
void SaveSelector(void) {
cpu.gdt.SetDescriptor(selector,desc);
}
void Get_SSx_ESPx(Bitu level,Bitu & _ss,Bitu & _esp) {
cpu.mpl=0;
if (is386) {
PhysPt where=base+offsetof(TSS_32,esp0)+level*8;
_esp=mem_readd(where);
_ss=mem_readw(where+4);
} else {
PhysPt where=base+offsetof(TSS_16,sp0)+level*4;
_esp=mem_readw(where);
_ss=mem_readw(where+2);
}
cpu.mpl=3;
}
bool SetSelector(Bitu new_sel) {
valid=false;
if ((new_sel & 0xfffc)==0) {
selector=0;
base=0;
limit=0;
is386=1;
return true;
}
if (new_sel&4) return false;
if (!cpu.gdt.GetDescriptor(new_sel,desc)) return false;
switch (desc.Type()) {
case DESC_286_TSS_A: case DESC_286_TSS_B:
case DESC_386_TSS_A: case DESC_386_TSS_B:
break;
default:
return false;
}
if (!desc.saved.seg.p) return false;
selector=new_sel;
valid=true;
base=desc.GetBase();
limit=desc.GetLimit();
is386=desc.Is386();
return true;
}
void SaveState( std::ostream& stream );
void LoadState( std::istream& stream );
TSS_Descriptor desc;
Bitu selector;
PhysPt base;
Bitu limit;
Bitu is386;
bool valid;
};
TaskStateSegment cpu_tss;
enum TSwitchType {
TSwitch_JMP,TSwitch_CALL_INT,TSwitch_IRET
};
bool CPU_SwitchTask(Bitu new_tss_selector,TSwitchType tstype,Bitu old_eip) {
bool old_allow = dosbox_allow_nonrecursive_page_fault;
/* this code isn't very easy to make interruptible. so temporarily revert to recursive PF handling method */
dosbox_allow_nonrecursive_page_fault = false;
FillFlags();
TaskStateSegment new_tss;
if (!new_tss.SetSelector(new_tss_selector))
E_Exit("Illegal TSS for switch, selector=%x, switchtype=%lx",(int)new_tss_selector,(unsigned long)tstype);
if (tstype==TSwitch_IRET) {
if (!new_tss.desc.IsBusy())
E_Exit("TSS not busy for IRET");
} else {
if (new_tss.desc.IsBusy())
E_Exit("TSS busy for JMP/CALL/INT");
}
Bitu new_cr3=0;
Bitu new_eax,new_ebx,new_ecx,new_edx,new_esp,new_ebp,new_esi,new_edi;
Bitu new_es,new_cs,new_ss,new_ds,new_fs,new_gs;
Bitu new_ldt,new_eip,new_eflags;
/* Read new context from new TSS */
if (new_tss.is386) {
new_cr3=mem_readd(new_tss.base+offsetof(TSS_32,cr3));
new_eip=mem_readd(new_tss.base+offsetof(TSS_32,eip));
new_eflags=mem_readd(new_tss.base+offsetof(TSS_32,eflags));
new_eax=mem_readd(new_tss.base+offsetof(TSS_32,eax));
new_ecx=mem_readd(new_tss.base+offsetof(TSS_32,ecx));
new_edx=mem_readd(new_tss.base+offsetof(TSS_32,edx));
new_ebx=mem_readd(new_tss.base+offsetof(TSS_32,ebx));
new_esp=mem_readd(new_tss.base+offsetof(TSS_32,esp));
new_ebp=mem_readd(new_tss.base+offsetof(TSS_32,ebp));
new_edi=mem_readd(new_tss.base+offsetof(TSS_32,edi));
new_esi=mem_readd(new_tss.base+offsetof(TSS_32,esi));
new_es=mem_readw(new_tss.base+offsetof(TSS_32,es));
new_cs=mem_readw(new_tss.base+offsetof(TSS_32,cs));
new_ss=mem_readw(new_tss.base+offsetof(TSS_32,ss));
new_ds=mem_readw(new_tss.base+offsetof(TSS_32,ds));
new_fs=mem_readw(new_tss.base+offsetof(TSS_32,fs));
new_gs=mem_readw(new_tss.base+offsetof(TSS_32,gs));
new_ldt=mem_readw(new_tss.base+offsetof(TSS_32,ldt));
} else {
E_Exit("286 task switch");
new_cr3=0;
new_eip=0;
new_eflags=0;
new_eax=0; new_ecx=0; new_edx=0; new_ebx=0;
new_esp=0; new_ebp=0; new_edi=0; new_esi=0;
new_es=0; new_cs=0; new_ss=0; new_ds=0; new_fs=0; new_gs=0;
new_ldt=0;
}
/* Check if we need to clear busy bit of old TASK */
if (tstype==TSwitch_JMP || tstype==TSwitch_IRET) {
cpu_tss.desc.SetBusy(false);
cpu_tss.SaveSelector();
}
Bit32u old_flags = reg_flags;
if (tstype==TSwitch_IRET) old_flags &= (~FLAG_NT);
/* Save current context in current TSS */
if (cpu_tss.is386) {
mem_writed(cpu_tss.base+offsetof(TSS_32,eflags),old_flags);
mem_writed(cpu_tss.base+offsetof(TSS_32,eip),old_eip);
mem_writed(cpu_tss.base+offsetof(TSS_32,eax),reg_eax);
mem_writed(cpu_tss.base+offsetof(TSS_32,ecx),reg_ecx);
mem_writed(cpu_tss.base+offsetof(TSS_32,edx),reg_edx);
mem_writed(cpu_tss.base+offsetof(TSS_32,ebx),reg_ebx);
mem_writed(cpu_tss.base+offsetof(TSS_32,esp),reg_esp);
mem_writed(cpu_tss.base+offsetof(TSS_32,ebp),reg_ebp);
mem_writed(cpu_tss.base+offsetof(TSS_32,esi),reg_esi);
mem_writed(cpu_tss.base+offsetof(TSS_32,edi),reg_edi);
mem_writed(cpu_tss.base+offsetof(TSS_32,es),SegValue(es));
mem_writed(cpu_tss.base+offsetof(TSS_32,cs),SegValue(cs));
mem_writed(cpu_tss.base+offsetof(TSS_32,ss),SegValue(ss));
mem_writed(cpu_tss.base+offsetof(TSS_32,ds),SegValue(ds));
mem_writed(cpu_tss.base+offsetof(TSS_32,fs),SegValue(fs));
mem_writed(cpu_tss.base+offsetof(TSS_32,gs),SegValue(gs));
} else {
E_Exit("286 task switch");
}
/* Setup a back link to the old TSS in new TSS */
if (tstype==TSwitch_CALL_INT) {
if (new_tss.is386) {
mem_writed(new_tss.base+offsetof(TSS_32,back),cpu_tss.selector);
} else {
mem_writew(new_tss.base+offsetof(TSS_16,back),cpu_tss.selector);
}
/* And make the new task's eflag have the nested task bit */
new_eflags|=FLAG_NT;
}
/* Set the busy bit in the new task */
if (tstype==TSwitch_JMP || tstype==TSwitch_CALL_INT) {
new_tss.desc.SetBusy(true);
new_tss.SaveSelector();
}
// cpu.cr0|=CR0_TASKSWITCHED;
if (new_tss_selector == cpu_tss.selector) {
reg_eip = old_eip;
new_cs = SegValue(cs);
new_ss = SegValue(ss);
new_ds = SegValue(ds);
new_es = SegValue(es);
new_fs = SegValue(fs);
new_gs = SegValue(gs);
} else {
/* Setup the new cr3 */
if (paging.cr3 != new_cr3)
// if they are the same it is not flushed
// according to the 386 manual
PAGING_SetDirBase(new_cr3);
/* Load new context */
if (new_tss.is386) {
reg_eip=new_eip;
CPU_SetFlags(new_eflags,FMASK_ALL | FLAG_VM);
reg_eax=new_eax;
reg_ecx=new_ecx;
reg_edx=new_edx;
reg_ebx=new_ebx;
reg_esp=new_esp;
reg_ebp=new_ebp;
reg_edi=new_edi;
reg_esi=new_esi;
// new_cs=mem_readw(new_tss.base+offsetof(TSS_32,cs));
} else {
E_Exit("286 task switch");
}
}
/* Load the new selectors */
if (reg_flags & FLAG_VM) {
SegSet16(cs,new_cs);
cpu.code.big=false;
CPU_SetCPL(3); //We don't have segment caches so this will do
} else {
/* Protected mode task */
if (new_ldt!=0) CPU_LLDT(new_ldt);
/* Load the new CS*/
Descriptor cs_desc;
CPU_SetCPL(new_cs & 3);
if (!cpu.gdt.GetDescriptor(new_cs,cs_desc))
E_Exit("Task switch with CS beyond limits");
if (!cs_desc.saved.seg.p)
E_Exit("Task switch with non present code-segment");
switch (cs_desc.Type()) {
case DESC_CODE_N_NC_A: case DESC_CODE_N_NC_NA:
case DESC_CODE_R_NC_A: case DESC_CODE_R_NC_NA:
if (cpu.cpl != cs_desc.DPL()) E_Exit("Task CS RPL != DPL");
goto doconforming;
case DESC_CODE_N_C_A: case DESC_CODE_N_C_NA:
case DESC_CODE_R_C_A: case DESC_CODE_R_C_NA:
if (cpu.cpl < cs_desc.DPL()) E_Exit("Task CS RPL < DPL");
doconforming:
Segs.expanddown[cs]=cs_desc.GetExpandDown();
Segs.limit[cs]=do_seg_limits?cs_desc.GetLimit():(~0UL);
Segs.phys[cs]=cs_desc.GetBase();
cpu.code.big=cs_desc.Big()>0;
Segs.val[cs]=new_cs;
break;
default:
E_Exit("Task switch CS Type %d",(int)cs_desc.Type());
}
}
CPU_SetSegGeneral(es,new_es);
CPU_SetSegGeneral(ss,new_ss);
CPU_SetSegGeneral(ds,new_ds);
CPU_SetSegGeneral(fs,new_fs);
CPU_SetSegGeneral(gs,new_gs);
if (!cpu_tss.SetSelector(new_tss_selector)) {
LOG(LOG_CPU,LOG_NORMAL)("TaskSwitch: set tss selector %X failed",new_tss_selector);
}
// cpu_tss.desc.SetBusy(true);
// cpu_tss.SaveSelector();
// LOG_MSG("Task CPL %X CS:%X IP:%X SS:%X SP:%X eflags %x",cpu.cpl,SegValue(cs),reg_eip,SegValue(ss),reg_esp,reg_flags);
dosbox_allow_nonrecursive_page_fault = old_allow;
return true;
}
bool CPU_IO_Exception(Bitu port,Bitu size) {
if (cpu.pmode && ((GETFLAG_IOPL<cpu.cpl) || GETFLAG(VM))) {
cpu.mpl=0;
if (!cpu_tss.is386) goto doexception;
PhysPt bwhere=cpu_tss.base+0x66;
Bitu ofs=mem_readw(bwhere);
if (ofs>cpu_tss.limit) goto doexception;
bwhere=cpu_tss.base+ofs+(port/8);
Bitu map=mem_readw(bwhere);
Bitu mask=(0xffff>>(16-size)) << (port&7);
if (map & mask) goto doexception;
cpu.mpl=3;
}
return false;
doexception:
cpu.mpl=3;
LOG(LOG_CPU,LOG_NORMAL)("IO Exception port %X",port);
return CPU_PrepareException(EXCEPTION_GP,0);
}
#include <stack>
int CPU_Exception_Level[0x20] = {0};
std::stack<int> CPU_Exception_In_Progress;
void CPU_Exception_Level_Reset() {
int i;
for (i=0;i < 0x20;i++)
CPU_Exception_Level[i] = 0;
while (!CPU_Exception_In_Progress.empty())
CPU_Exception_In_Progress.pop();
}
bool has_printed_double_fault = false;
bool has_printed_triple_fault = false;
bool always_report_double_fault = false;
bool always_report_triple_fault = false;
void On_Software_CPU_Reset();
void CPU_Exception(Bitu which,Bitu error ) {
assert(which < 0x20);
// LOG_MSG("Exception %d error %x",which,error);
if (CPU_Exception_Level[which] != 0) {
if (CPU_Exception_Level[EXCEPTION_DF] != 0 && cpu_triple_fault_reset) {
if (always_report_triple_fault || !has_printed_triple_fault) {
LOG_MSG("CPU_Exception: Double fault already in progress == Triple Fault. Resetting CPU.");
has_printed_triple_fault = true;
}
// Triple fault -> special shutdown cycle -> reset signal -> reset.
// Sickening, I know, but that's how IBM wired things a long long time ago.
On_Software_CPU_Reset();
E_Exit("Triple fault reset call unexpectedly returned");
}
if (always_report_double_fault || !has_printed_double_fault) {
LOG_MSG("CPU_Exception: Exception %d already in progress, triggering double fault instead",(int)which);
has_printed_double_fault = true;
}
which = EXCEPTION_DF;
error = 0;
}
if (cpu_double_fault_enable) {
/* CPU_Interrupt() could cause another fault during memory access. This needs to happen here */
CPU_Exception_Level[which]++;
CPU_Exception_In_Progress.push(which);
}
cpu.exception.error=error;
CPU_Interrupt(which,CPU_INT_EXCEPTION | ((which>=8) ? CPU_INT_HAS_ERROR : 0),reg_eip);
/* allow recursive page faults. required for multitasking OSes like Windows 95.
* we set this AFTER CPU_Interrupt so that if CPU_Interrupt faults while starting
* a page fault we still trigger double fault. */
if (which == EXCEPTION_PF || which == EXCEPTION_GP) {
if (CPU_Exception_Level[which] > 0)
CPU_Exception_Level[which]--;
if (!CPU_Exception_In_Progress.empty()) {
if ((Bitu)CPU_Exception_In_Progress.top() == which)
CPU_Exception_In_Progress.pop();
else
LOG_MSG("Top of fault stack not the same as what I'm handling");
}
}
}
Bit8u lastint;
void CPU_Interrupt(Bitu num,Bitu type,Bitu oldeip) {
lastint=num;
FillFlags();
#if C_DEBUG
# if C_HEAVY_DEBUG
bool DEBUG_IntBreakpoint(Bit8u intNum);
Bitu DEBUG_EnableDebugger(void);
if (type != CPU_INT_SOFTWARE) { /* CPU core already takes care of SW interrupts */
if (DEBUG_IntBreakpoint(num))
DEBUG_EnableDebugger();
}
# endif
switch (num) {
case 0xcd:
#if C_HEAVY_DEBUG
LOG(LOG_CPU,LOG_ERROR)("Call to interrupt 0xCD this is BAD");
DEBUG_HeavyWriteLogInstruction();
E_Exit("Call to interrupt 0xCD this is BAD");
#endif
break;
case 0x03:
if (DEBUG_Breakpoint()) {
CPU_Cycles=0;
return;
}
};
#endif
if (!cpu.pmode) {
/* Save everything on a 16-bit stack */
CPU_Push16(reg_flags & 0xffff);
CPU_Push16(SegValue(cs));
CPU_Push16(oldeip);
SETFLAGBIT(IF,false);
SETFLAGBIT(TF,false);
/* Get the new CS:IP from vector table */
PhysPt base=cpu.idt.GetBase();
reg_eip=mem_readw(base+(num << 2));
Segs.val[cs]=mem_readw(base+(num << 2)+2);
Segs.phys[cs]=Segs.val[cs]<<4;
if (!cpu_allow_big16) cpu.code.big=false;
return;
} else {
/* Protected Mode Interrupt */
if ((reg_flags & FLAG_VM) && (type&CPU_INT_SOFTWARE) && !(type&CPU_INT_NOIOPLCHECK)) {
// LOG_MSG("Software int in v86, AH %X IOPL %x",reg_ah,(reg_flags & FLAG_IOPL) >>12);
if ((reg_flags & FLAG_IOPL)!=FLAG_IOPL) {
CPU_Exception(EXCEPTION_GP,0);
return;
}
}
Descriptor gate;
if (!cpu.idt.GetDescriptor(num<<3,gate)) {
// zone66
CPU_Exception(EXCEPTION_GP,num*8+2+(type&CPU_INT_SOFTWARE)?0:1);
return;
}
if ((type&CPU_INT_SOFTWARE) && (gate.DPL()<cpu.cpl)) {
// zone66, win3.x e
CPU_Exception(EXCEPTION_GP,num*8+2);
return;
}
Bitu old_esp,old_ss,old_cpl;
old_esp = reg_esp;
old_ss = SegValue(ss);
old_cpl = cpu.cpl;
try {
switch (gate.Type()) {
case DESC_286_INT_GATE: case DESC_386_INT_GATE:
case DESC_286_TRAP_GATE: case DESC_386_TRAP_GATE:
{
CPU_CHECK_COND(!gate.saved.seg.p,
"INT:Gate segment not present",
EXCEPTION_NP,num*8+2+(type&CPU_INT_SOFTWARE)?0:1)
Descriptor cs_desc;
Bitu gate_sel=gate.GetSelector();
Bitu gate_off=gate.GetOffset();
CPU_CHECK_COND((gate_sel & 0xfffc)==0,
"INT:Gate with CS zero selector",
EXCEPTION_GP,(type&CPU_INT_SOFTWARE)?0:1)
CPU_CHECK_COND(!cpu.gdt.GetDescriptor(gate_sel,cs_desc),
"INT:Gate with CS beyond limit",
EXCEPTION_GP,(gate_sel & 0xfffc)+(type&CPU_INT_SOFTWARE)?0:1)
Bitu cs_dpl=cs_desc.DPL();
CPU_CHECK_COND(cs_dpl>cpu.cpl,
"Interrupt to higher privilege",
EXCEPTION_GP,(gate_sel & 0xfffc)+(type&CPU_INT_SOFTWARE)?0:1)
switch (cs_desc.Type()) {
case DESC_CODE_N_NC_A: case DESC_CODE_N_NC_NA:
case DESC_CODE_R_NC_A: case DESC_CODE_R_NC_NA:
if (cs_dpl<cpu.cpl) {
/* Prepare for gate to inner level */
CPU_CHECK_COND(!cs_desc.saved.seg.p,
"INT:Inner level:CS segment not present",
EXCEPTION_NP,(gate_sel & 0xfffc)+(type&CPU_INT_SOFTWARE)?0:1)
CPU_CHECK_COND((reg_flags & FLAG_VM) && (cs_dpl!=0),
"V86 interrupt calling codesegment with DPL>0",
EXCEPTION_GP,gate_sel & 0xfffc)
Bitu n_ss,n_esp;
Bitu o_ss,o_esp;
o_ss=SegValue(ss);
o_esp=reg_esp;
cpu_tss.Get_SSx_ESPx(cs_dpl,n_ss,n_esp);
CPU_CHECK_COND((n_ss & 0xfffc)==0,
"INT:Gate with SS zero selector",
EXCEPTION_TS,(type&CPU_INT_SOFTWARE)?0:1)
Descriptor n_ss_desc;
CPU_CHECK_COND(!cpu.gdt.GetDescriptor(n_ss,n_ss_desc),
"INT:Gate with SS beyond limit",
EXCEPTION_TS,(n_ss & 0xfffc)+(type&CPU_INT_SOFTWARE)?0:1)
CPU_CHECK_COND(((n_ss & 3)!=cs_dpl) || (n_ss_desc.DPL()!=cs_dpl),
"INT:Inner level with CS_DPL!=SS_DPL and SS_RPL",
EXCEPTION_TS,(n_ss & 0xfffc)+(type&CPU_INT_SOFTWARE)?0:1)
// check if stack segment is a writable data segment
switch (n_ss_desc.Type()) {
case DESC_DATA_EU_RW_NA: case DESC_DATA_EU_RW_A:
case DESC_DATA_ED_RW_NA: case DESC_DATA_ED_RW_A:
break;
default:
E_Exit("INT:Inner level:Stack segment not writable."); // or #TS(ss_sel+EXT)
}
CPU_CHECK_COND(!n_ss_desc.saved.seg.p,
"INT:Inner level with nonpresent SS",
EXCEPTION_SS,(n_ss & 0xfffc)+(type&CPU_INT_SOFTWARE)?0:1)
// commit point
Segs.expanddown[ss]=n_ss_desc.GetExpandDown();
Segs.limit[ss]=do_seg_limits?n_ss_desc.GetLimit():(~0UL);
Segs.phys[ss]=n_ss_desc.GetBase();
Segs.val[ss]=n_ss;
if (n_ss_desc.Big()) {
cpu.stack.big=true;
cpu.stack.mask=0xffffffff;
cpu.stack.notmask=0;
reg_esp=n_esp;
} else {
cpu.stack.big=false;
cpu.stack.mask=0xffff;
cpu.stack.notmask=0xffff0000;
reg_sp=n_esp & 0xffff;
}
CPU_SetCPL(cs_dpl);
if (gate.Type() & 0x8) { /* 32-bit Gate */
if (reg_flags & FLAG_VM) {
CPU_Push32(SegValue(gs));SegSet16(gs,0x0);
CPU_Push32(SegValue(fs));SegSet16(fs,0x0);
CPU_Push32(SegValue(ds));SegSet16(ds,0x0);
CPU_Push32(SegValue(es));SegSet16(es,0x0);
}
CPU_Push32(o_ss);
CPU_Push32(o_esp);
} else { /* 16-bit Gate */