-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathear.c
2231 lines (1888 loc) · 60 KB
/
ear.c
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
//
// ear.c
// PegasusEar
//
// Created by Kevin Colley on 3/25/17.
//
#include "ear.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include "common/macros.h"
#ifdef EAR_DEVEL
#define DEVEL_RETURN(ear, reason) do { \
if((ear)->debug_flags & DEBUG_VERBOSE) { \
fprintf(stderr, STRINGIFY(reason) "(" STRINGIFY(__LINE__) ")\n"); \
} \
return (reason); \
} while(0)
#else /* EAR_DEVEL */
#define DEVEL_RETURN(ear, reason) do { \
return (reason); \
} while(0)
#endif /* EAR_DEVEL */
static inline EAR_PageTable* EAR_getPageTable(EAR* ear);
static inline EAR_PTE* EAR_getPTE(EAR* ear, EAR_PageNumber ppn);
static EAR_HaltReason EAR_invokeFaultHandler(EAR* ear, EAR_Size fault_handler, EAR_Size tte_paddr, EAR_Size vmaddr, EAR_Protection prot, EAR_Size* out_paddr);
static EAR_HaltReason EAR_translate(EAR* ear, EAR_Size vmaddr, EAR_Protection prot, EAR_Size* out_paddr);
static EAR_HaltReason EAR_readByte(EAR* ear, EAR_Size addr, EAR_Byte* out_byte);
static EAR_HaltReason EAR_writeByte(EAR* ear, EAR_Size addr, EAR_Byte byte);
static EAR_HaltReason EAR_readWord(EAR* ear, EAR_Size addr, EAR_Size* out_word);
static EAR_HaltReason EAR_writeWord(EAR* ear, EAR_Size addr, EAR_Size word);
static EAR_HaltReason EAR_fetchCodeByte(EAR* ear, EAR_Size* pc, EAR_Size dpc, uint8_t* out_byte);
static EAR_HaltReason EAR_fetchCodeImm16(EAR* ear, EAR_Size* pc, EAR_Size dpc, EAR_Size* out_value);
static EAR_HaltReason EAR_executeInstruction(EAR* ear, EAR_Instruction* insn);
volatile sig_atomic_t g_interrupted = 0;
static void interrupt_handler(int sig) {
(void)sig;
// Flag the process as having been interrupted
g_interrupted = 1;
}
static bool s_interrupt_handler_enabled = false;
static struct sigaction old_handler;
void enable_interrupt_handler(void) {
if(s_interrupt_handler_enabled) {
return;
}
g_interrupted = 0;
struct sigaction sa;
sa.sa_flags = SA_RESETHAND;
sa.sa_handler = &interrupt_handler;
sigaction(SIGINT, &sa, &old_handler);
s_interrupt_handler_enabled = true;
}
void disable_interrupt_handler(void) {
if(s_interrupt_handler_enabled) {
sigaction(SIGINT, &old_handler, NULL);
s_interrupt_handler_enabled = false;
}
}
static inline EAR_PageTable* EAR_getPageTable(EAR* ear) {
return (EAR_PageTable*)&ear->mem.bytes[EAR_TTB_PADDR];
}
static inline EAR_PTE* EAR_getPTE(EAR* ear, EAR_PageNumber ppn) {
EAR_PTE* phat = &ear->mem.bytes[EAR_PHYSICAL_ALLOCATION_TABLE_PADDR];
return &phat[ppn];
}
static EAR_HaltReason EAR_invokeFaultHandler(EAR* ear, EAR_Size fault_handler, EAR_Size tte_paddr, EAR_Size vmaddr, EAR_Protection prot, EAR_Size* out_paddr) {
EAR_HaltReason ret = HALT_NONE;
// Zero-initialize the exception context
uint64_t saved_inscount = ear->exc_ctx.ins_count;
memset(&ear->exc_ctx, 0, sizeof(ear->exc_ctx));
ear->exc_ctx.ins_count = saved_inscount;
// Set the disable MMU flag
ear->exc_ctx.flags |= FLAG_MF;
// Replace PC register value with the address of the currently executing instruction
EAR_Size next_pc = ear->context.r[PC];
ear->context.r[PC] = ear->context.cur_pc;
// Copy register values from standard stack to the top of the exception stack
EAR_Size saved_regs = EAR_EXCEPTION_STACK_TOP - sizeof(ear->context.r);
memcpy(&ear->mem.bytes[saved_regs], ear->context.r, sizeof(ear->context.r));
// Set SP to the top of the exception stack location
ear->exc_ctx.r[SP] = saved_regs;
// Set the exception context as the CPU's active thread context
ear->active = &ear->exc_ctx;
// Convert prot from a bit value to a number
EAR_Size protnum = 0;
switch(prot) {
case EAR_PROT_READ:
protnum = 0;
break;
case EAR_PROT_WRITE:
protnum = 1;
break;
case EAR_PROT_EXECUTE:
protnum = 2;
break;
default:
ASSERT(false);
}
// Invoke page fault handler function
ret = EAR_invokeFunction(ear, fault_handler, 0, tte_paddr, vmaddr, protnum, saved_regs, next_pc, 0, true);
if(ret != HALT_NONE) {
ear->active = &ear->context;
return ret;
}
// Use fault handler function's return value as the resulting physical address
*out_paddr = ear->active->r[RV];
// Copy register values back into the standard context
memcpy(&ear->context.r, &ear->mem.bytes[saved_regs], sizeof(ear->context.r));
// If the fault handler stores a nonzero value in the the saved ZERO register, the
// faulting instruction is marked as complete and it will not attempt to finish executing.
if(ear->context.r[ZERO] != 0) {
ear->context.r[ZERO] = 0;
ret = HALT_COMPLETE;
}
// Fix up PC
if(ear->context.r[PC] != ear->context.cur_pc) {
ret = HALT_COMPLETE;
}
// Swap back to using the standard context as the CPU's active thread context
ear->active = &ear->context;
#if EAR_CLEAR_EXCEPTION_STACK
// Clear exception stack upon return from exception handler function
memset(&ear->mem.bytes[EAR_EXCEPTION_STACK_BOTTOM], 0, EAR_EXCEPTION_STACK_SIZE);
#endif /* EAR_CLEAR_EXCEPTION_STACK */
// Will return either HALT_NONE or HALT_COMPLETE
return ret;
}
/*! Translate an attempt to access a virtual address with the given type of access into
* the physical address backing that address and the virtual address of a function to be
* called to handle page faults.
*
* @param vmaddr Virtual address to translate
* @param prot Attempted access permissions, exactly one of read, write, or execute
* @param out_paddr Output variable that will hold the physical address of the memory
* page that backs this virtual address.
* @return HALT_NONE if translation succeeds, halt reason otherwise.
*/
static EAR_HaltReason EAR_translate(EAR* ear, EAR_Size vmaddr, EAR_Protection prot, EAR_Size* out_paddr) {
EAR_PageTable* ttable = EAR_getPageTable(ear);
EAR_TTE* tte = &ttable->entries[EAR_PAGE_NUMBER(vmaddr)];
EAR_PageNumber ppn;
EAR_HaltReason r = HALT_NONE;
// Default output value
*out_paddr = 0;
// Disable MMU?
if(ear->active->flags & FLAG_MF) {
ppn = EAR_PAGE_NUMBER(vmaddr);
}
else {
// Look up physical page number based on the requested access type
if(prot == EAR_PROT_READ) {
ppn = tte->r_ppn;
}
else if(prot == EAR_PROT_WRITE) {
ppn = tte->w_ppn;
}
else if(prot == EAR_PROT_EXECUTE) {
ppn = tte->x_ppn;
}
else {
ASSERT(false);
}
// Is this virtual page mapped with the requested access permission?
if(ppn == 0) {
// Are we already handling an exception?
if(ear->active == &ear->exc_ctx) {
r = HALT_DOUBLEFAULT;
}
// See if there's a fault handler function to run when in invasive mode
if(r == HALT_NONE && ((ear->debug_flags & DEBUG_NOFAULT) || tte->fault_ppn == 0)) {
r = HALT_UNMAPPED;
}
// Invoke external fault handler if registered and there's no internal fault handler for this TTE
if(r != HALT_NONE) {
if(ear->fault_fn != NULL) {
return ear->fault_fn(ear->fault_cookie, vmaddr, prot, tte, r, out_paddr);
}
// No internal or external fault handler, so raise the halt reason
return r;
}
// Calculate physical address of this TTE
EAR_Size tte_paddr = EAR_TTB_PADDR + EAR_PAGE_NUMBER(vmaddr) * sizeof(EAR_TTE);
// Call fault handler function to resolve the virtual memory access
return EAR_invokeFaultHandler(ear, (EAR_Size)tte->fault_ppn * EAR_PAGE_SIZE, tte_paddr, vmaddr, prot, out_paddr);
}
}
// Check if the physical page is accessible
EAR_PTE* pte = EAR_getPTE(ear, EAR_PAGE_NUMBER(vmaddr));
if(!(*pte & PHYS_ALLOW)) {
return HALT_DOUBLEFAULT;
}
// Compute output physical address
*out_paddr = (EAR_Size)ppn * EAR_PAGE_SIZE + EAR_PAGE_OFFSET(vmaddr);
return HALT_NONE;
}
/*!
* @brief Initialize an EAR CPU with default register values and memory mappings
*
* @param debugFlags Initial debug flags
*/
void EAR_init(EAR* ear, EAR_DebugFlags debugFlags) {
EAR_PageNumber ppn;
memset(ear, 0, sizeof(*ear));
// Set initial debug flags early as they may affect some of the later code
ear->debug_flags = debugFlags;
// Mark NULL phys page as used but not allowed
*EAR_getPTE(ear, EAR_PAGE_NUMBER(EAR_NULL)) = PHYS_IN_USE | PHYS_DENY;
// Mark PHAT as used and allowed
*EAR_getPTE(ear, EAR_PAGE_NUMBER(EAR_PHYSICAL_ALLOCATION_TABLE_PADDR)) = PHYS_IN_USE | PHYS_ALLOW;
// Add page table region mirroring its physical address range
EAR_PageNumber pageTablePPNs[sizeof(EAR_PageTable) / EAR_PAGE_SIZE];
unsigned i;
for(i = 0; i < ARRAY_COUNT(pageTablePPNs); i++) {
pageTablePPNs[i] = EAR_PAGE_NUMBER(EAR_TTB_PADDR) + i;
*EAR_getPTE(ear, pageTablePPNs[i]) = PHYS_IN_USE | PHYS_ALLOW;
}
EAR_addSegment(ear, EAR_TTB_PADDR, sizeof(EAR_PageTable), pageTablePPNs, EAR_PROT_READ | EAR_PROT_WRITE, EAR_NULL);
// Add exception stack top guard and set the fault vmaddr to itself to mark it as used
*EAR_getPTE(ear, EAR_PAGE_NUMBER(EAR_EXCEPTION_STACK_TOP_GUARD)) = PHYS_IN_USE | PHYS_DENY;
// Add exception stack contents
for(ppn = EAR_PAGE_NUMBER(EAR_EXCEPTION_STACK_BOTTOM); ppn != EAR_PAGE_NUMBER(EAR_EXCEPTION_STACK_TOP); ppn++) {
*EAR_getPTE(ear, ppn) = PHYS_IN_USE | PHYS_ALLOW;
}
// Add exception stack bottom guard
*EAR_getPTE(ear, EAR_PAGE_NUMBER(EAR_EXCEPTION_STACK_BOTTOM_GUARD)) = PHYS_IN_USE | PHYS_DENY;
// The initial physical page layout is complete. It is now safe to use EAR_allocPhys().
// Add virtual stack top guard page
EAR_addSegment(ear, EAR_STACK_TOP_GUARD, EAR_PAGE_SIZE, NULL, EAR_PROT_NONE, EAR_EXCEPTION_STACK_TOP_GUARD);
// Add virtual stack region
EAR_PageNumber stackPPNs[EAR_STACK_SIZE / EAR_PAGE_SIZE];
if(EAR_allocPhys(ear, ARRAY_COUNT(stackPPNs), stackPPNs) != ARRAY_COUNT(stackPPNs)) {
abort();
}
EAR_addSegment(
ear,
EAR_STACK_BOTTOM, //vmaddr
EAR_STACK_SIZE, //vmsize
stackPPNs, //physaddr
EAR_PROT_READ | EAR_PROT_WRITE, //vmprot
EAR_NULL //fault_vmaddr
);
// Add virtual stack bottom guard
EAR_addSegment(ear, EAR_STACK_BOTTOM_GUARD, EAR_PAGE_SIZE, NULL, EAR_PROT_NONE, EAR_EXCEPTION_STACK_BOTTOM_GUARD);
// Init registers
EAR_resetRegisters(ear);
// Set standard thread state as the active one
ear->active = &ear->context;
}
/*! Reset the normal thread state to its default values */
void EAR_resetRegisters(EAR* ear) {
memset(&ear->context, 0, sizeof(ear->context));
// Set RA and RD to determine when the program tries to return from a top-level function
ear->context.r[RA] = EAR_CALL_RA;
ear->context.r[RD] = EAR_CALL_RD;
// All registers are initially zero
ear->context.flags = FLAG_ZF;
// Set stack registers
ear->context.r[SP] = EAR_STACK_TOP;
ear->context.r[FP] = EAR_STACK_TOP;
}
/*!
* @brief Add a memory segment that should be accessible to the EAR CPU
*
* @param vmaddr Virtual memory address where the segment starts, or NULL to
* find the next available virtual region that's large enough
* @param vmsize Size of the memory segment in virtual memory space
* @param phys_page_array Array of physical addresses for this region, or NULL
* to set them all to EAR_NULL
* @param vmprot Memory protections for this new virtual memory segment
* @param fault_physaddr Physical address of function called to handle page faults
*
* @return Virtual address where the segment was mapped. Usually vmaddr unless
* it was passed as EAR_NULL. If a virtual region could not be found
* that was large enough to hold the requested size, then EAR_NULL is
* returned.
*/
EAR_Size EAR_addSegment(EAR* ear, EAR_Size vmaddr, EAR_Size vmsize, const EAR_PageNumber* phys_page_array, EAR_Protection vmprot, EAR_Size fault_physaddr) {
if(ear->debug_flags & DEBUG_VERBOSE) {
fprintf(stderr, "addSegment(0x%x, 0x%x, *..., 0x%x, 0x%x)\n", vmaddr, vmsize, vmprot, fault_physaddr);
}
// vmaddr, vmsize, and fault_physaddr must be page-aligned
if(
!EAR_IS_PAGE_ALIGNED(vmaddr) ||
!EAR_IS_PAGE_ALIGNED(vmsize) ||
!EAR_IS_PAGE_ALIGNED(fault_physaddr)
) {
abort();
}
// Does vmsize extend beyond the end of the address space?
if((uint32_t)vmaddr + (uint32_t)vmsize > EAR_ADDRESS_SPACE_SIZE) {
abort();
}
// Locate the translation table
EAR_PageTable* ttable = EAR_getPageTable(ear);
EAR_PageNumber page_count = EAR_PAGE_NUMBER(vmsize);
// Search for a vm region?
if(vmaddr == EAR_NULL) {
EAR_PageNumber vpn_start = 1, vpn_end = 1;
while(vpn_start < 256 - vmsize / EAR_PAGE_SIZE) {
EAR_TTE* tte = &ttable->entries[vpn_start];
// Is vpn_start an unmapped virtual page?
if(tte->r_ppn == EAR_NULL && tte->w_ppn == EAR_NULL && tte->x_ppn == EAR_NULL && tte->fault_ppn == EAR_NULL) {
// Start searching subsequent virtual pages to see if the virtual region is large enough
vpn_end = vpn_start + 1;
while(vpn_end - vpn_start < page_count) {
tte = &ttable->entries[vpn_end];
// Is vpn_end a mapped page?
if(!(tte->r_ppn == EAR_NULL && tte->w_ppn == EAR_NULL && tte->x_ppn == EAR_NULL && tte->fault_ppn == EAR_NULL)) {
// Found a mapped page before getting enough pages for the requested allocation,
// so advance vpn_start and keep searching
break;
}
vpn_end++;
}
// Found virtual region?
if(vpn_end - vpn_start < page_count) {
// Did not find a large enough region, so advance the start and try again
vpn_start = vpn_end + 1;
continue;
}
else {
break;
}
}
vpn_start++;
}
// Found virtual region?
if(vpn_end - vpn_start == page_count) {
vmaddr = vpn_start * EAR_PAGE_SIZE;
}
else {
return EAR_NULL;
}
}
EAR_PageNumber vpn = EAR_PAGE_NUMBER(vmaddr);
EAR_PageNumber fault_ppn = EAR_PAGE_NUMBER(fault_physaddr);
EAR_TTE* cur = &ttable->entries[vpn];
// Update each translation table entry
while(page_count != 0) {
EAR_PageNumber ppn = phys_page_array ? *phys_page_array++ : 0;
// Set physical page numbers for each allowed access type
if(vmprot & EAR_PROT_READ) {
cur->r_ppn = ppn;
}
if(vmprot & EAR_PROT_WRITE) {
cur->w_ppn = ppn;
}
if(vmprot & EAR_PROT_EXECUTE) {
cur->x_ppn = ppn;
}
// Set fault handler function pointer
cur->fault_ppn = fault_ppn;
// Advance to next page
page_count--;
cur++;
}
return vmaddr;
}
/*! Set the current thread state of the EAR CPU
* @param thstate New thread state
*/
void EAR_setThreadState(EAR* ear, const EAR_ThreadState* thstate) {
memcpy(&ear->context, thstate, sizeof(ear->context));
}
/*! Set the functions called to handle reads/writes on ports
* @param read_fn Function pointer called to handle `RDB`
* @param write_fn Function pointer called to handle `WRB`
* @param cookie Opaque value passed as the first parameter to these callbacks
*/
void EAR_setPorts(EAR* ear, EAR_PortRead* read_fn, EAR_PortWrite* write_fn, void* cookie) {
ear->read_fn = read_fn;
ear->write_fn = write_fn;
ear->port_cookie = cookie;
}
/*!
* @brief Set the callback function used to handle unhandled page faults
*
* @param fault_fn Function pointer called during an unhandled page fault
* @param cookie Opaque value passed to fault_fn
*/
void EAR_setFaultHandler(EAR* ear, EAR_FaultHandler* fault_fn, void* cookie) {
ear->fault_fn = fault_fn;
ear->fault_cookie = cookie;
}
/*!
* @brief Set the callback function used whenever an instruction accesses memory
*
* @param mem_fn Function pointer called during all memory accesses
* @param cookie Opaque value passed to mem_fn
*/
void EAR_setMemoryHook(EAR* ear, EAR_MemoryHook* mem_fn, void* cookie) {
ear->mem_fn = mem_fn;
ear->mem_cookie = cookie;
}
/*!
* @brief Set the callback function used when execution resumes
*
* @param debug_fn Function pointer called when execution is about to start
* @param cookie Opaque value passed to debug_fn
*/
void EAR_attachDebugger(EAR* ear, EAR_DebugAttach* debug_fn, void* cookie) {
ear->debug_fn = debug_fn;
ear->debug_cookie = cookie;
if(ear->debug_fn != NULL) {
ear->debug_flags |= DEBUG_ATTACH;
}
else {
ear->debug_flags &= ~DEBUG_ATTACH;
}
}
static EAR_HaltReason EAR_readByte(EAR* ear, EAR_Size addr, EAR_Byte* out_byte) {
EAR_HaltReason ret;
if(ear->mem_fn != NULL) {
ret = ear->mem_fn(ear->mem_cookie, addr, EAR_PROT_READ, sizeof(*out_byte), out_byte);
if(ret == HALT_COMPLETE) {
return HALT_NONE;
}
if(ret != HALT_NONE) {
return ret;
}
}
// Translate virtual to physical address
EAR_Size paddr;
ret = EAR_translate(ear, addr, EAR_PROT_READ, &paddr);
if(ret != HALT_NONE) {
// Return if there was an error during translation or if the current instruction
// is marked as completed by the fault handler function.
return ret;
}
// Read byte from physical memory
*out_byte = ear->mem.bytes[paddr];
return HALT_NONE;
}
static EAR_HaltReason EAR_writeByte(EAR* ear, EAR_Size addr, EAR_Byte byte) {
EAR_HaltReason ret;
if(ear->mem_fn != NULL) {
ret = ear->mem_fn(ear->mem_cookie, addr, EAR_PROT_WRITE, sizeof(byte), &byte);
if(ret == HALT_COMPLETE) {
return HALT_NONE;
}
if(ret != HALT_NONE) {
return ret;
}
}
// Translate virtual to physical address
EAR_Size paddr;
ret = EAR_translate(ear, addr, EAR_PROT_READ, &paddr);
if(ret != HALT_NONE) {
// Return if there was an error during translation or if the current instruction
// is marked as completed by the fault handler function.
return ret;
}
// Write byte to physical memory
ear->mem.bytes[paddr] = byte;
return HALT_NONE;
}
static EAR_HaltReason EAR_readWord(EAR* ear, EAR_Size addr, EAR_Size* out_word) {
EAR_HaltReason ret;
if(ear->mem_fn != NULL) {
ret = ear->mem_fn(ear->mem_cookie, addr, EAR_PROT_READ, sizeof(*out_word), out_word);
if(ret == HALT_COMPLETE) {
return HALT_NONE;
}
if(ret != HALT_NONE) {
return ret;
}
}
// Can only read words from an aligned address
if(addr % sizeof(*out_word) != 0) {
return HALT_UNALIGNED;
}
// Translate virtual to physical address
EAR_Size paddr;
ret = EAR_translate(ear, addr, EAR_PROT_READ, &paddr);
if(ret != HALT_NONE) {
// Return if there was an error during translation or if the current instruction
// is marked as completed by the fault handler function.
return ret;
}
// Read word from physical memory
memcpy(out_word, &ear->mem.bytes[paddr], sizeof(*out_word));
return HALT_NONE;
}
static EAR_HaltReason EAR_writeWord(EAR* ear, EAR_Size addr, EAR_Size word) {
EAR_HaltReason ret;
if(ear->mem_fn != NULL) {
ret = ear->mem_fn(ear->mem_cookie, addr, EAR_PROT_WRITE, sizeof(word), &word);
if(ret == HALT_COMPLETE) {
return HALT_NONE;
}
if(ret != HALT_NONE) {
return ret;
}
}
// Can only write words to an aligned address
if(addr % sizeof(word) != 0) {
return HALT_UNALIGNED;
}
// Translate virtual to physical address
EAR_Size paddr;
ret = EAR_translate(ear, addr, EAR_PROT_WRITE, &paddr);
if(ret != HALT_NONE) {
// Return if there was an error during translation or if the current instruction
// is marked as completed by the fault handler function.
return ret;
}
// Write word to physical memory
memcpy(&ear->mem.bytes[paddr], &word, sizeof(word));
return HALT_NONE;
}
static EAR_HaltReason EAR_fetchCodeByte(EAR* ear, EAR_Size* pc, EAR_Size dpc, EAR_Byte* out_byte) {
EAR_HaltReason ret;
if(ear->mem_fn != NULL) {
ret = ear->mem_fn(ear->mem_cookie, *pc, EAR_PROT_EXECUTE, sizeof(*out_byte), out_byte);
if(ret == HALT_COMPLETE) {
return HALT_NONE;
}
if(ret != HALT_NONE) {
return ret;
}
}
// Look up memory page in which PC is located
EAR_Size code_paddr;
ret = EAR_translate(ear, *pc, EAR_PROT_EXECUTE, &code_paddr);
if(ret != HALT_NONE) {
return ret;
}
// Read byte from page offset
*out_byte = ear->mem.bytes[code_paddr];
// Update PC
*pc += 1 + dpc;
return HALT_NONE;
}
static EAR_HaltReason EAR_fetchCodeImm16(EAR* ear, EAR_Size* pc, EAR_Size dpc, EAR_Size* out_value) {
EAR_HaltReason ret;
EAR_Byte imm_byte;
*out_value = 0;
// Fetch first byte (low byte)
ret = EAR_fetchCodeByte(ear, pc, dpc, &imm_byte);
if(ret != HALT_NONE) {
return ret;
}
// Store low byte
*out_value = imm_byte;
// Fetch second byte (high byte)
ret = EAR_fetchCodeByte(ear, pc, dpc, &imm_byte);
if(ret != HALT_NONE) {
return ret;
}
// Store high byte
*out_value |= imm_byte << 8;
return HALT_NONE;
}
/*!
* @brief Fetch the next code instruction starting at *pc.
*
* @param pc In/out pointer to the address of the instruction to fetch. This will
* be written back with the address of the code byte directly following
* the instruction that was fetched.
* @param dpc Value of DPC register (delta PC) to use while fetching code bytes
* @param out_insn Output pointer where the decoded instruction will be written
* @return Reason for halting, typically HALT_NONE
*/
EAR_HaltReason EAR_fetchInstruction(EAR* ear, EAR_Size* pc, EAR_Size dpc, EAR_Instruction* out_insn) {
EAR_Byte ins_byte = 0;
EAR_HaltReason ret = HALT_NONE;
EAR_Cond cond = 0;
EAR_Opcode op = 0;
bool hasDRPrefix = false;
// Zero-initialize instruction
memset(out_insn, 0, sizeof(*out_insn));
// Handle instruction prefixes
do {
// Read instruction byte
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Decode first instruction byte
cond = ins_byte >> 5;
op = ins_byte & 0x1f;
// Check if this is an instruction prefix byte
if(cond != COND_SP || out_insn->cond) {
break;
}
if(op == PREFIX_XC) {
// Extended Condition prefix, set high bit in condition code
if(out_insn->cond & 0x8) {
// Doesn't make sense to allow multiple XC prefixes
DEVEL_RETURN(ear, HALT_DECODE);
}
out_insn->cond |= 0x8;
}
else if(op == PREFIX_TF) {
if(out_insn->toggle_flags) {
// Doesn't make sense to allow multiple TF prefixes
DEVEL_RETURN(ear, HALT_DECODE);
}
out_insn->toggle_flags = true;
}
else if(op == PREFIX_EM) {
if(out_insn->enable_mmu) {
// Doesn't make sense to allow multiple EM prefixes
DEVEL_RETURN(ear, HALT_DECODE);
}
out_insn->enable_mmu = true;
}
else if(op & PREFIX_DR_MASK) {
// Destination Register prefix, set destination register
if(hasDRPrefix) {
// Doesn't make sense to allow multiple DR prefixes
DEVEL_RETURN(ear, HALT_DECODE);
}
hasDRPrefix = true;
out_insn->rd = op & 0x0F;
}
else {
// Invalid instruction prefix
DEVEL_RETURN(ear, HALT_DECODE);
}
} while(cond == COND_SP);
// Write condition code and opcode to the decoded instruction
out_insn->cond |= cond;
out_insn->op = op;
// Decode additional instruction-specific bytes (if any)
switch(out_insn->op) {
case OP_PSH:
case OP_POP: // PSH/POP {Regs16}
if(!hasDRPrefix) {
// Rd is usually SP unless overridden
out_insn->rd = SP;
}
// Read low byte of regs16
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Set low byte of regs16
out_insn->regs16 = ins_byte;
// Read high byte of regs16
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Set high byte of regs16
out_insn->regs16 |= ins_byte << 8;
return HALT_NONE;
case OP_ADD:
case OP_SUB:
case OP_MLU:
case OP_MLS:
case OP_DVU:
case OP_DVS:
case OP_XOR:
case OP_AND:
case OP_ORR:
case OP_SHL:
case OP_SRU:
case OP_SRS:
case OP_MOV:
case OP_CMP:
case OP_LDW:
case OP_STW:
case OP_LDB:
case OP_BRA:
case OP_FCA: // INSN [Rd,] Rx, Vy
// Read Rx:Ry byte
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Extract Rx and Ry register numbers
out_insn->rx = ins_byte >> 4;
out_insn->ry = ins_byte & 0x0F;
// Set Rd, defaulting to Rx unless there was an DR prefix
if(!hasDRPrefix) {
out_insn->rd = out_insn->rx;
}
else if(op >= OP_MOV) {
// DR prefix is pointless
DEVEL_RETURN(ear, HALT_DECODE);
}
if(op == OP_CMP) {
// CMP is handled just like SUB except Rd is set to ZERO so the result is discarded
out_insn->rd = ZERO;
}
// When Ry is DPC, there will be an immediate value after the opcode byte
if(out_insn->ry != DPC) {
// Read register value
out_insn->ry_val = ear->active->r[out_insn->ry];
return HALT_NONE;
}
// Read immediate value
return EAR_fetchCodeImm16(ear, pc, dpc, &out_insn->ry_val);
case OP_BRR:
case OP_FCR: // BRR/FCR <label> (encoded as Imm16)
// DR prefix is pointless
if(hasDRPrefix) {
DEVEL_RETURN(ear, HALT_DECODE);
}
// Read Imm16
return EAR_fetchCodeImm16(ear, pc, dpc, &out_insn->ry_val);
case OP_RDB: // RDB Rx, (portnum)
// DR prefix is pointless
if(hasDRPrefix) {
DEVEL_RETURN(ear, HALT_DECODE);
}
// Read Rx:Imm4 byte
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Extract Rx register number and port number
out_insn->rx = ins_byte >> 4;
out_insn->port_number = ins_byte & 0x0F;
out_insn->rd = out_insn->rx;
return HALT_NONE;
case OP_STB:
case OP_WRB:
// DR prefix is pointless
if(hasDRPrefix) {
DEVEL_RETURN(ear, HALT_DECODE);
}
// Read Imm4:Ry byte or Rx:Ry byte
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Extract Rx or port number and Ry register number
if(op == OP_STB) {
// STB sets Rx but not port number
out_insn->rx = ins_byte >> 4;
}
else {
// WRB sets port number but not Rx
out_insn->port_number = ins_byte >> 4;
}
out_insn->ry = ins_byte & 0x0F;
// If Ry is DPC, there's another byte for the immediate
if(out_insn->ry != DPC) {
out_insn->ry_val = ear->active->r[out_insn->ry];
return HALT_NONE;
}
// Read immediate byte (Imm8)
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Set value of Ry to the Imm8
out_insn->ry_val = ins_byte;
return HALT_NONE;
case OP_INC: // INC Rx, imm4
// Read byte containing Rx and imm4
ret = EAR_fetchCodeByte(ear, pc, dpc, &ins_byte);
if(ret != HALT_NONE) {
return ret;
}
// Extract Rx and Imm4 (increment amount)
out_insn->rx = ins_byte >> 4;
out_insn->ry_val = ins_byte & 0x0F;
// Sign extend ry_val
if(ins_byte & 0x08) {
out_insn->ry_val |= 0xFFF0;
}
// Is the SImm4 value positive?
if(out_insn->ry_val <= 7) {
// No need to allow INC 0, so increment non-negative values to expand
// the positive max value to 8
++out_insn->ry_val;
}
// If there's no DR prefix, then default Rd to Rx
if(!hasDRPrefix) {
out_insn->rd = out_insn->rx;
}
return HALT_NONE;
case OP_BPT:
case OP_HLT:
case OP_NOP: // Single-byte instructions
// DR prefix is pointless
if(hasDRPrefix) {
DEVEL_RETURN(ear, HALT_DECODE);
}
return HALT_NONE;
default:
if(ear->debug_flags & DEBUG_VERBOSE) {
fprintf(stderr, "Invalid opcode: 0x%02X (0x%02X)\n", op, ins_byte);
}
DEVEL_RETURN(ear, HALT_DECODE);
}
}
static bool EAR_evaluateCondition(EAR* ear, EAR_Cond cond) {
EAR_Flag flags = ear->active->flags;
switch(cond) {
case COND_EQ: //COND := ZF
return !!(flags & FLAG_ZF);
case COND_NE: //COND := !ZF
return !(flags & FLAG_ZF);
case COND_GT: //COND := CF and !ZF
return (flags & (FLAG_ZF | FLAG_CF)) == FLAG_CF;
case COND_LE: //COND := !CF or ZF
return !(flags & FLAG_CF) || !!(flags & FLAG_ZF);
case COND_LT: //COND := !CF
return !(flags & FLAG_CF);
case COND_GE: //COND := CF
return !!(flags & FLAG_CF);
case COND_AL: //COND := true
return true;
case COND_NG: //COND := SF
return !!(flags & FLAG_SF);
case COND_PS: //COND := !SF
return !(flags & FLAG_SF);
case COND_BG: //COND := !ZF and (SF == VF)
return !(flags & FLAG_ZF) && ((flags & FLAG_SF) == (flags & FLAG_VF));
case COND_SE: //COND := ZF or (SF != VF)
return !!(flags & FLAG_ZF) || ((flags & FLAG_SF) != (flags & FLAG_VF));
case COND_SM: //COND := SF != VF
return (flags & FLAG_SF) != (flags & FLAG_VF);
case COND_BE: //COND := SF == VF
return (flags & FLAG_SF) == (flags & FLAG_VF);
case COND_OD: //COND := PF
return !!(flags & FLAG_PF);
case COND_EV: //COND := !PF
return !(flags & FLAG_PF);
case COND_SP:
default:
abort();
}
}
static EAR_HaltReason EAR_executeInstruction(EAR* ear, EAR_Instruction* insn) {
EAR_HaltReason ret = HALT_NONE;