Implement 386 instruction table, improve 8086/186/286 instruction table
[multi_emu.git] / emu_8086.c
1 #include <stdbool.h>
2 #include <stdint.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #if ALT_BACKEND
7 #include "virtualxt/lib/vxt/cpu.h"
8 #else
9 #include "cpu_8086.h"
10 #endif
11
12 #define REG_TRACE 1
13 #define MEM_TRACE 1
14 #define IO_TRACE 1
15
16 #define MEM_SIZE 0x100000
17 uint8_t mem[MEM_SIZE];
18
19 // read and write are separated for I/O
20 #define IO_SIZE 0x100
21 uint8_t io_read[IO_SIZE];
22 uint8_t io_write[IO_SIZE];
23
24 int load_ihx(char *name) {
25   FILE *fp = fopen(name, "r");
26   if (fp == NULL) {
27     perror(name);
28     exit(EXIT_FAILURE);
29   }
30
31   int base = 0, entry_point = 0;
32   bool had_eof = false;
33   char line[0x100];
34   while (fgets(line, 0x100, fp)) {
35     for (char *p = line; *p; ++p)
36       if (*p == '\n') {
37         *p = 0;
38         break;
39       }
40
41     if (had_eof) {
42       fprintf(stderr, "garbage after EOF record: %s\n", line);
43       exit(EXIT_FAILURE);
44     }
45
46     if (line[0] != ':') {
47       fprintf(stderr, "require colon: %s\n", line);
48       exit(EXIT_FAILURE);
49     }
50
51     uint8_t buf[0x7f];
52     int len;
53     for (len = 0; len < 0x7f; ++len) {
54       char *p = line + 1 + len * 2;
55       if (*p == 0)
56         break;
57       if (*p == '\n') {
58         *p = 0;
59         break;
60       }
61       uint8_t c = p[2];
62       p[2] = 0;
63
64       char *q;
65       buf[len] = (uint8_t)strtol(p, &q, 16);
66       p[2] = c;
67       if (q != p + 2) {
68         fprintf(stderr, "not hex byte: %s\n", p);
69         exit(EXIT_FAILURE);
70       }
71     }
72
73     if (len == 0) {
74       fprintf(stderr, "empty line: %s\n", line);
75       exit(EXIT_FAILURE);
76     }
77
78     uint8_t checksum = 0;
79     for (int i = 0; i < len; ++i)
80       checksum += buf[i];
81     if (checksum) {
82       checksum -= buf[len - 1];
83       fprintf(
84         stderr,
85         "checksum %02x, should be %02x\n",
86         checksum,
87         buf[len - 1]
88       );
89       exit(EXIT_FAILURE);
90     }
91
92     len -= 5;
93     if (len != buf[0]) {
94       fprintf(stderr, "incorrect length: %s\n", line);
95       exit(EXIT_FAILURE);
96     }
97
98     int addr = (buf[1] << 8) | buf[2], end_addr;
99     switch (buf[3]) {
100     case 0:
101       addr += base;
102       end_addr = addr + len;
103       if (end_addr < addr || end_addr > MEM_SIZE) {
104         fprintf(stderr, "invalid load range: [0x%x, 0x%x)\n", addr, end_addr);
105         exit(EXIT_FAILURE);
106       }
107       memcpy(mem + addr, buf + 4, len);
108       break;
109     case 1:
110       had_eof = true;
111       break;
112     case 3:
113       if (len < 4) {
114         fprintf(stderr, "invalid start address record: %s\n", line);
115         exit(EXIT_FAILURE);
116       }
117       entry_point = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
118       break;
119     case 4:
120       if (len < 2) {
121         fprintf(stderr, "invalid extended linear address record: %s\n", line);
122         exit(EXIT_FAILURE);
123       }
124       base = (buf[4] << 24) | (buf[5] << 16);
125       break;
126 #if 0
127     case 5:
128       if (len < 4) {
129         fprintf(stderr, "invalid start linear address record: %s\n", line);
130         exit(EXIT_FAILURE);
131       }
132       entry_point = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
133       break;
134 #endif
135     default:
136       fprintf(stderr, "unknown record type: 0x%x\n", buf[3]);
137       exit(EXIT_FAILURE);
138     }
139   }
140   if (!had_eof) {
141     fprintf(stderr, "no EOF record\n");
142     exit(EXIT_FAILURE);
143   }
144
145   fclose(fp);
146   return entry_point;
147 }
148
149 int msdos(int ax, int dx, int ds, bool *zf) {
150   switch (ax >> 8) {
151   case 2:
152     // Character output
153     dx &= 0xff;
154     switch (dx) {
155     case '\n':
156       break;
157     case '\r':
158       dx = '\n';
159     default:
160       putchar(dx);
161       break;
162     }
163     break;
164   case 6:
165     // Direct console I/O
166     dx &= 0xff;
167     if (dx == 0xff) {
168       int data = getchar();
169       switch (data) {
170       case '\n':
171         data = '\r';
172         goto avail;
173       case 0x7f:
174         data = '\b';
175         //goto avail;
176       default:
177       avail:
178         ax = data | 0x600;
179         *zf = true;
180         break;
181       case EOF:
182         *zf = false;
183         break;
184       }
185       break;
186     }
187     switch (dx) {
188     case '\n':
189       break;
190     case '\r':
191       dx = '\n';
192     default:
193       putchar(dx);
194       break;
195     }
196     break;
197   case 9:
198     // Display string
199     for (
200       int data;
201       (data = mem[dx + (ds << 4)]) != '$';
202       dx = (dx + 1) & 0xffff
203     )
204       switch (data) {
205       case '\n':
206         break;
207       case '\r':
208         data = '\n';
209       default:
210         putchar(data);
211         break;
212       }
213     break;
214   case 0x26:
215     // Create PSP -- ignore
216     break;
217   case 0x30:
218     // Get DOS version
219     ax = 0x211;
220     break;
221   case 0x33:
222     // Get or set Ctrl-Break -- ignore
223     break;
224   case 0x25:
225     // Set interrupt vector -- ignore
226     break;
227   case 0x35:
228     // Get interrupt vector -- ignore
229     break;
230   default:
231     fprintf(stderr, "unimplemented MSDOS call 0x%02x\n", ax >> 8);
232     exit(EXIT_FAILURE);
233   }
234   return ax;
235 }
236
237 int read_byte(void *context, int addr) {
238 #if MEM_TRACE
239   int data = mem[addr];
240   fprintf(stderr, "addr=%05x rd=%02x\n", addr, data);
241   return data;
242 #else
243   return mem[addr];
244 #endif
245 }
246
247 void write_byte(void *context, int addr, int data) {
248 #if MEM_TRACE
249   fprintf(stderr, "addr=%05x wr=%02x\n", addr, data);
250 #endif
251   mem[addr] = data;
252 }
253
254 int in_byte(void *context, int addr) {
255   addr &= 0xff;
256 #if IO_TRACE
257   int data = io_read[addr];
258   printf("io=%02x rd=%02x\n", addr, data);
259   return data;
260 #else
261   return io_read[addr];
262 #endif
263 }
264
265 void out_byte(void *context, int addr, int data) {
266   addr &= 0xff;
267 #if IO_TRACE
268   printf("io=%02x wr=%02x\n", addr, data);
269 #endif
270   io_write[addr] = data;
271 }
272
273 #if ALT_BACKEND
274 #if defined(VXT_LIBC) && !defined(VXT_NO_LOGGING)
275 #include <stdarg.h>
276 static int libc_print(const char *fmt, ...) {
277   va_list args;
278   va_start(args, fmt);
279   int ret = vprintf(fmt, args);
280   va_end(args);
281   return ret;
282 }
283 int (*logger)(const char*, ...) = &libc_print;
284 #else
285 static int no_print(const char *fmt, ...) {
286   UNUSED(fmt);
287   return -1;
288 }
289 int (*logger)(const char*, ...) = &no_print;
290 #endif
291
292 static void no_breakpoint(void) {}
293 void (*breakpoint)(void) = &no_breakpoint;
294
295 vxt_byte vxt_system_read_byte(CONSTP(vxt_system) s, vxt_pointer addr) {
296   addr &= 0xFFFFF;
297   return read_byte(NULL, addr);
298 }
299
300 void vxt_system_write_byte(CONSTP(vxt_system) s, vxt_pointer addr, vxt_byte data) {
301   addr &= 0xFFFFF;
302   write_byte(NULL, addr, data);
303 }
304
305 vxt_word vxt_system_read_word(CONSTP(vxt_system) s, vxt_pointer addr) {
306   int data = vxt_system_read_byte(s, addr);
307   return WORD(vxt_system_read_byte(s, addr + 1), data);
308 }
309
310 void vxt_system_write_word(CONSTP(vxt_system) s, vxt_pointer addr, vxt_word data) {
311   vxt_system_write_byte(s, addr, LBYTE(data));
312   vxt_system_write_byte(s, addr + 1, HBYTE(data));
313 }
314
315 vxt_byte system_in(CONSTP(vxt_system) s, vxt_word port) {
316   return in_byte(NULL, port);
317 }
318
319 void system_out(CONSTP(vxt_system) s, vxt_word port, vxt_byte data) {
320   out_byte(NULL, port, data);
321 }
322 #endif
323
324 int main(int argc, char **argv) {
325   if (argc < 2) {
326     printf("usage: %s image.ihx\n", argv[0]);
327     exit(EXIT_FAILURE);
328   }
329   int entry_point = load_ihx(argv[1]);
330
331   // int 0x20 vector
332   mem[0x80] = 0;
333   mem[0x81] = 0;
334   mem[0x82] = 0x40;
335   mem[0x83] = 0;
336   mem[0x400] = 0xcf; // iret, for msdos emulation
337
338   // int 0x21 vector
339   mem[0x84] = 1;
340   mem[0x85] = 0;
341   mem[0x86] = 0x40;
342   mem[0x87] = 0;
343   mem[0x401] = 0xcf; // iret, for msdos emulation
344
345   // psp
346   int code_segment = (entry_point >> 16) & 0xffff;
347   mem[5 + (code_segment << 4)] = 0xc3; // ret, for bdos emulation
348   mem[6 + (code_segment << 4)] = 0xfe; // memory top, lo byte
349   mem[7 + (code_segment << 4)] = 0xff; // memory top, hi byte
350   mem[0x80 + (code_segment << 4)] = 0; // command line length
351   mem[0x81 + (code_segment << 4)] = '\r'; // command line sentinel
352
353 #if ALT_BACKEND
354   struct cpu cpu;
355   memset(&cpu, 0, sizeof(struct cpu));
356   cpu_reset(&cpu);
357   //cpu.read_byte = rb;
358   //cpu.write_byte = wb;
359   //cpu.port_in = in;
360   //cpu.port_out = out;
361   cpu.regs.ip = entry_point & 0xffff;
362   cpu.regs.cs = code_segment;
363   cpu.regs.sp = 0xfffe;
364   cpu.regs.ss = code_segment;
365
366   while (true) {
367 #if REG_TRACE
368     fprintf(
369       stderr,
370       "ip=%04x ax=%04x cx=%04x dx=%04x bx=%04x sp=%04x bp=%04x si=%04x di=%04x cs=%04x ds=%04x es=%04x ss=%04x flags=%04x cf=%d pf=%d af=%d zf=%d sf=%d tf=%d if=%d df=%d of=%d\n",
371       cpu.regs.ip,
372       cpu.regs.ax,
373       cpu.regs.cx,
374       cpu.regs.dx,
375       cpu.regs.bx,
376       cpu.regs.sp,
377       cpu.regs.bp,
378       cpu.regs.si,
379       cpu.regs.di,
380       cpu.regs.cs,
381       cpu.regs.ds,
382       cpu.regs.es,
383       cpu.regs.ss,
384       cpu.regs.flags,
385       cpu.regs.flags & 1,
386       (cpu.regs.flags >> 2) & 1,
387       (cpu.regs.flags >> 4) & 1,
388       (cpu.regs.flags >> 6) & 1,
389       (cpu.regs.flags >> 7) & 1,
390       (cpu.regs.flags >> 8) & 1,
391       (cpu.regs.flags >> 9) & 1,
392       (cpu.regs.flags >> 10) & 1,
393       (cpu.regs.flags >> 11) & 1
394     );
395 #endif
396
397     if (
398       (cpu.regs.ip == 0 && cpu.regs.cs == code_segment) ||
399       (cpu.regs.ip == 0 && cpu.regs.cs == 0x40)
400     ) {
401       putchar('\n');
402       break;
403     }
404     if (cpu.regs.ip == 5 && cpu.regs.cs == code_segment) {
405       bool zf = (cpu.regs.flags >> 6) & 1;
406       cpu.regs.ax = msdos(cpu.regs.cl << 8, cpu.regs.dx, cpu.regs.ds, &zf);
407       cpu.regs.flags = (zf << 6) | (cpu.regs.flags & ~0x40);
408     }
409     else if (cpu.regs.ip == 1 && cpu.regs.cs == 0x40) {
410       bool zf = (cpu.regs.flags >> 6) & 1;
411       cpu.regs.ax = msdos(cpu.regs.ax, cpu.regs.dx, cpu.regs.ds, &zf);
412       cpu.regs.flags = (zf << 6) | (cpu.regs.flags & ~0x40);
413     }
414     cpu_step(&cpu);
415   }
416 #else
417   struct cpu_8086 cpu;
418   cpu_8086_init(
419     &cpu,
420     read_byte,
421     NULL,
422     write_byte,
423     NULL,
424     in_byte,
425     NULL,
426     out_byte,
427     NULL
428   );
429   cpu_8086_reset(&cpu);
430   cpu.regs.word.pc = entry_point;
431
432   while (true) {
433 #if REG_TRACE
434     fprintf(
435       stderr,
436       "pc=%04x af=%04x bc=%04x de=%04x hl=%04x sp=%04x ix=%04x iy=%04x cf=%d nf=%d pvf=%d hf=%d zf=%d sf=%d\n",
437       cpu.regs.word.pc,
438       cpu.regs.word.af,
439       cpu.regs.word.bc,
440       cpu.regs.word.de,
441       cpu.regs.word.hl,
442       cpu.regs.word.sp,
443       cpu.regs.word.ix,
444       cpu.regs.word.iy,
445       cpu.regs.bit.cf,
446       cpu.regs.bit.nf,
447       cpu.regs.bit.pvf,
448       cpu.regs.bit.hf,
449       cpu.regs.bit.zf,
450       cpu.regs.bit.sf
451     );
452 #endif
453
454     if (cpu.regs.word.pc == 0) {
455       putchar('\n');
456       break;
457     }
458     if (cpu.regs.word.pc == 5) {
459       cpu.regs.word.de = bdos(
460         cpu.regs.byte.c,
461         cpu.regs.word.de
462       );
463     }
464     cpu_8086_execute(&cpu);
465   }
466 #endif
467
468   return 0;
469 }