1 /*-----------------------------------------------------------------------*\
2 | bios.c -- CP/M emulator "front-end" for the Z80 emulator -- runs |
3 | standard CP/M executables with no changes as long as they use CP/M |
4 | system calls to do all their work. |
5 | |
6 | Originally by Kevin Kayes, but he refused any responsibility for it, |
7 | then I later hacked it up, enhanced it, and debugged it. |
8 | |
9 | Copyright 1986-1988 by Parag Patel. All Rights Reserved. |
10 | Copyright 1994-1995,2000 by CodeGen, Inc. All Rights Reserved. |
11 \*-----------------------------------------------------------------------*/
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <ctype.h>
16 #include <string.h>
17 #include <time.h>
18 #include <sys/types.h>
19 #include "cpmdisc.h"
20 #include "defs.h"
21
22 #ifdef macintosh
23 #include <stat.h>
24 #else
25 #include <sys/stat.h>
26 #endif
27
28 /* definition of: extern unsigned char cpm_array[]; */
29 #include "cpm.c"
30
31 /* The BDOS/CCP had better be built with these values! */
32 #define CCP 0xD400 /* (BIOS - 0x1600) */
33 #define BDOS 0xDC00
34 #define CBIOS 0xEA00
35
36 #define NENTRY 30 /* number of BIOS entries */
37 #define STACK 0xEF00 /* grows down from here */
38
39 /* The first NUMHDISCS drives may be specified as hard-drives. */
40 #define NUMHDISCS 2
41 #define NUMDISCS (MAXDISCS - NUMHDISCS)
42
43 #if NUMHDISCS > MAXDISCS
44 # error Too many hard-discs specified here.
45 #endif
46
47 /* disk parameter block information */
48 #define DPBSIZE 15
49 #define HDPBLOCK (CBIOS + NENTRY * 8)
50 #define DPBLOCK (HDPBLOCK + DPBSIZE)
51 #define DIRBUF (DPBLOCK + DPBSIZE)
52 #define DPHSIZE 16
53
54 /* hard disc parameter info */
55 #define HDPBASE (DIRBUF + SECTORSIZE)
56
57 #define DPBASE (HDPBASE + DPHSIZE * NUMHDISCS)
58 #define CVSIZE 0
59 #define ALVSIZE 33
60 #define CVBASE (DPBASE + DPHSIZE * NUMDISCS)
61 #define ALVBASE (CVBASE + CVSIZE * NUMDISCS)
62
63 /* ST-506 allocation vector size */
64 #define HALVBASE (ALVBASE + ALVSIZE * NUMDISCS)
65 #define HALVSIZE 306
66
67 /* buffer for where to return time/date info */
68 #define TIMEBUF (HALVBASE + HALVSIZE * NUMHDISCS)
69 #define TIMEBUFSIZE 5
70
71 /* just a marker for future expansion */
72 #define END_OF_BIOS (TIMEBUF + TIMEBUFSIZE)
73
74
75 /* ST-506 HD sector info (floppy defs are in cpmdisc.h for makedisc.c) */
76 #define HDSECTORSPERTRACK 64
77 #define HDTRACKSPERDISC 610
78
79 /* offsets into FCB needed for reading/writing Unix files */
80 #define FDOFFSET 12
81 #define BLKOFFSET 16
82 #define SZOFFSET 20
83
84 /* this batch of macros are not used at the moment */
85 #define CBUFF CCP+7
86 #define CIBUFF CCP+8
87 #define DEFAULTFCB 0x005C
88 #define DEFAULTBUF 0x0080
89 #define IOBYTE 0x0003
90 #define DISK 0x0004
91 #define DMA 0x0008
92 #define USER 0x000A
93 #define USERSTART 0x0100
94
95
96 /* forward declarations: */
97 static void seldisc(z80info *z80);
98
99
100 static void
closeall(z80info * z80)101 closeall(z80info *z80)
102 {
103 int i;
104
105 for (i = 0; i < MAXDISCS; i++)
106 {
107 if (z80->drives[i] != NULL)
108 {
109 fclose(z80->drives[i]);
110 z80->drives[i] = NULL;
111 }
112 }
113 }
114
115 void
warmboot(z80info * z80)116 warmboot(z80info *z80)
117 {
118 int i;
119
120 closeall(z80);
121
122 if (silent_exit) {
123 finish(z80);
124 }
125
126 /* load CCP and BDOS into memory (max 0x1600 in size) */
127 for (i = 0; i < 0x1600 && i < sizeof cpm_array; i++)
128 SETMEM(CCP + i, cpm_array[i]);
129
130 /* try to load CCP/BDOS from disk, but ignore any errors */
131 loadfile(z80, "bdos.hex");
132 loadfile(z80, "ccp.hex");
133
134 /* CP/M system reset via "JP 00" - entry into BIOS warm-boot */
135 SETMEM(0x0000, 0xC3); /* JP CBIOS+3 */
136 SETMEM(0x0001, ((CBIOS + 3) & 0xFF));
137 SETMEM(0x0002, ((CBIOS + 3) >> 8));
138
139 /* 0x0003 is the IOBYTE, 0x0004 is the current DISK */
140 SETMEM(0x0003, 0x00);
141 SETMEM(0x0004, z80->drive);
142
143 /* CP/M syscall via "CALL 05" - entry into BDOS */
144 SETMEM(0x0005, 0xC3); /* JP BDOS+6 */
145 SETMEM(0x0006, ((BDOS+6) & 0xFF));
146 SETMEM(0x0007, ((BDOS+6) >> 8));
147
148 /* fake BIOS entry points */
149 for (i = 0; i < NENTRY; i++)
150 {
151 /* JP <bios-entry> */
152 SETMEM(CBIOS + 3 * i, 0xC3);
153 SETMEM(CBIOS + 3 * i + 1, (CBIOS + NENTRY * 3 + i * 5) & 0xFF);
154 SETMEM(CBIOS + 3 * i + 2, (CBIOS + NENTRY * 3 + i * 5) >> 8);
155
156 /* LD A,<bios-call> - start of bios-entry */
157 SETMEM(CBIOS + NENTRY * 3 + i * 5, 0x3E);
158 SETMEM(CBIOS + NENTRY * 3 + i * 5 + 1, i);
159
160 /* OUT A,0FFH - we use port 0xFF to fake the BIOS call */
161 SETMEM(CBIOS + NENTRY * 3 + i * 5 + 2, 0xD3);
162 SETMEM(CBIOS + NENTRY * 3 + i * 5 + 3, 0xFF);
163
164 /* RET - end of bios-entry */
165 SETMEM(CBIOS + NENTRY * 3 + i * 5 + 4, 0xC9);
166 }
167
168 /* disc parameter block - a 5Mb ST-506 hard disc */
169 SETMEM(HDPBLOCK, HDSECTORSPERTRACK & 0xFF);/* SPT - sectors per track */
170 SETMEM(HDPBLOCK + 1, HDSECTORSPERTRACK >> 8);
171 SETMEM(HDPBLOCK + 2, 4); /* BSH - data block shift factor */
172 SETMEM(HDPBLOCK + 3, 15); /* BLM - data block mask */
173 SETMEM(HDPBLOCK + 4, 0); /* EXM - extent mask */
174 SETMEM(HDPBLOCK + 5, 2441 & 0xFF); /* DSM - total drive capacity */
175 SETMEM(HDPBLOCK + 6, 2441 >> 8);
176 SETMEM(HDPBLOCK + 7, 1023 & 0xFF); /* DRM - total dir entries */
177 SETMEM(HDPBLOCK + 8, 1023 >> 8);
178 SETMEM(HDPBLOCK + 9, 0xFF); /* AL0 - blocks for directory entries */
179 SETMEM(HDPBLOCK + 10, 0xFF); /* AL1 */
180 SETMEM(HDPBLOCK + 11, 0x00); /* CKS - directory check vector */
181 SETMEM(HDPBLOCK + 12, 0x00);
182 SETMEM(HDPBLOCK + 13, RESERVEDTRACKS & 0xFF);/* OFF - reserved tracks */
183 SETMEM(HDPBLOCK + 14, RESERVEDTRACKS >> 8);
184
185 /* disk parameter headers for hard disc */
186 for (i = 0; i < NUMHDISCS; i++)
187 {
188 SETMEM(HDPBASE + DPHSIZE * i, 0x00); /* XLT */
189 SETMEM(HDPBASE + DPHSIZE * i + 1, 0x00);
190 SETMEM(HDPBASE + DPHSIZE * i + 2, 0x00); /* scratch 1 */
191 SETMEM(HDPBASE + DPHSIZE * i + 3, 0x00);
192 SETMEM(HDPBASE + DPHSIZE * i + 4, 0x00); /* scratch 2 */
193 SETMEM(HDPBASE + DPHSIZE * i + 5, 0x00);
194 SETMEM(HDPBASE + DPHSIZE * i + 6, 0x00); /* scratch 3 */
195 SETMEM(HDPBASE + DPHSIZE * i + 7, 0x00);
196 SETMEM(HDPBASE + DPHSIZE * i + 8, DIRBUF & 0xFF); /* DIRBUF */
197 SETMEM(HDPBASE + DPHSIZE * i + 9, DIRBUF >> 8);
198 SETMEM(HDPBASE + DPHSIZE * i + 10, HDPBLOCK & 0xFF); /* DPB */
199 SETMEM(HDPBASE + DPHSIZE * i + 11, HDPBLOCK >> 8);
200 SETMEM(HDPBASE + DPHSIZE * i + 12, 0x00);
201 SETMEM(HDPBASE + DPHSIZE * i + 13, 0x00);
202 SETMEM(HDPBASE + DPHSIZE * i + 14,
203 (HALVBASE + HALVSIZE * i) & 0xFF);
204 SETMEM(HDPBASE + DPHSIZE * i + 15,
205 (HALVBASE + HALVSIZE * i) >> 8);
206 }
207
208 /* disc parameter block - a single-sided single-density 8" 256k disc */
209 SETMEM(DPBLOCK, SECTORSPERTRACK & 0xFF); /* SPT - sectors per track */
210 SETMEM(DPBLOCK + 1, SECTORSPERTRACK >> 8);
211 SETMEM(DPBLOCK + 2, 3); /* BSH - data block shift factor */
212 SETMEM(DPBLOCK + 3, 7); /* BLM - data block mask */
213 SETMEM(DPBLOCK + 4, 0); /* EXM - extent mask */
214 SETMEM(DPBLOCK + 5, 242); /* DSM - total capacity of drive */
215 SETMEM(DPBLOCK + 6, 0);
216 SETMEM(DPBLOCK + 7, 63); /* DRM - total directory entries */
217 SETMEM(DPBLOCK + 8, 0);
218 SETMEM(DPBLOCK + 9, 0xC0); /* AL0 - blocks for directory entries */
219 SETMEM(DPBLOCK + 10, 0x00); /* AL1 */
220 SETMEM(DPBLOCK + 11, 0x00); /* CKS - directory check vector */
221 SETMEM(DPBLOCK + 12, 0x00);
222 SETMEM(DPBLOCK + 13, RESERVEDTRACKS & 0xFF); /* OFF - reserved tracks */
223 SETMEM(DPBLOCK + 14, RESERVEDTRACKS >> 8);
224
225 /* disc parameter headers */
226 for (i = 0; i < NUMDISCS; i++)
227 {
228 SETMEM(DPBASE + DPHSIZE * i, 0x00); /* XLT */
229 SETMEM(DPBASE + DPHSIZE * i + 1, 0x00);
230 SETMEM(DPBASE + DPHSIZE * i + 2, 0x00); /* scratch 1 */
231 SETMEM(DPBASE + DPHSIZE * i + 3, 0x00);
232 SETMEM(DPBASE + DPHSIZE * i + 4, 0x00); /* scratch 2 */
233 SETMEM(DPBASE + DPHSIZE * i + 5, 0x00);
234 SETMEM(DPBASE + DPHSIZE * i + 6, 0x00); /* scratch 3 */
235 SETMEM(DPBASE + DPHSIZE * i + 7, 0x00);
236 SETMEM(DPBASE + DPHSIZE * i + 8, DIRBUF & 0xFF); /* DIRBUF */
237 SETMEM(DPBASE + DPHSIZE * i + 9, DIRBUF >> 8);
238 SETMEM(DPBASE + DPHSIZE * i + 10, DPBLOCK & 0xFF); /* DPB */
239 SETMEM(DPBASE + DPHSIZE * i + 11, DPBLOCK >> 8);
240 #if (CVSIZE == 0)
241 SETMEM(DPBASE + DPHSIZE * i + 12, 0x00);
242 SETMEM(DPBASE + DPHSIZE * i + 13, 0x00);
243 #else
244 SETMEM(DPBASE + DPHSIZE * i + 12,
245 (CVBASE + CVSIZE * i) & 0xFF);
246 SETMEM(DPBASE + DPHSIZE * i + 13,
247 (CVBASE + CVSIZE * i) >> 8);
248 #endif
249 SETMEM(DPBASE + DPHSIZE * i + 14,
250 (ALVBASE + ALVSIZE * i) & 0xFF);
251 SETMEM(DPBASE + DPHSIZE * i + 15,
252 (ALVBASE + ALVSIZE * i) >> 8);
253 }
254
255 /* set up the stack for an 8-level RET to do a system reset */
256 SP = STACK;
257
258 for (i = 0; i < 8; i++)
259 {
260 /* push reboot entry (CBIOS + 3) onto stack */
261 --SP;
262 SETMEM(SP, (CBIOS + 3) >> 8);
263 --SP;
264 SETMEM(SP, (CBIOS + 3) & 0xFF);
265 }
266
267 /* set up the default disk (A:) and dma address */
268 z80->dma = 0x0080;
269
270 /* and all our default drive info */
271 z80->track = 0;
272 z80->sector = 1;
273
274 /* make sure the current file/disk is open */
275 B = 0;
276 C = z80->drive;
277 seldisc(z80);
278
279 PC = CCP;
280 }
281
282 static void
boot(z80info * z80)283 boot(z80info *z80)
284 {
285 z80->drive = 0;
286 warmboot(z80);
287 }
288
289 void
sysreset(z80info * z80)290 sysreset(z80info *z80)
291 {
292 boot(z80);
293 }
294
295 static void
consstat(z80info * z80)296 consstat(z80info *z80)
297 {
298 input(z80, 0x01, 0x01, &A);
299 }
300
301 static void
consin(z80info * z80)302 consin(z80info *z80)
303 {
304 input(z80, 0x00, 0x00, &A);
305
306 /* What is this for? It messing up Ctrl-S...
307 if (A == CNTL('S'))
308 input(z80, 0x00, 0x00, &A);
309 */
310 }
311
312 static void
consout(z80info * z80)313 consout(z80info *z80)
314 {
315 output(z80, 0x00, 0x00, C & 0x7F);
316 }
317
318 /* list character in C */
319 static void
list(z80info * z80)320 list(z80info *z80)
321 {
322 static FILE *fp = NULL;
323
324 if (fp == NULL)
325 {
326 fp = fopen("list", "w");
327
328 if (fp == NULL)
329 return;
330 }
331
332 /* close up on EOF */
333 if (C == CNTL('D') || C == '\0')
334 {
335 fclose(fp);
336 fp = NULL;
337 return;
338 }
339
340 putc(C, fp);
341 }
342
343 /* punch character in C */
344 static void
punch(z80info * z80)345 punch(z80info *z80)
346 {
347 }
348
349 /* return reader char in A, ^Z is EOF */
350 static void
reader(z80info * z80)351 reader(z80info *z80)
352 {
353 A = CNTL('Z');
354 }
355
356 static void
home(z80info * z80)357 home(z80info *z80)
358 {
359 z80->track = 0;
360 z80->sector = 1;
361 }
362
363 /* Open disk image */
364
365 static void
realizedisk(z80info * z80)366 realizedisk(z80info *z80)
367 {
368 int drive = z80->drive;
369 char drivestr[80];
370
371 strcpy(drivestr, drive < NUMHDISCS ? "A-Hdrive" : "A-drive");
372 drivestr[0] += drive; /* set the 1st letter to the drive name */
373
374 if (z80->drives[drive] == NULL)
375 {
376 struct stat statbuf;
377 long secs;
378 FILE *fp;
379
380 fp = fopen(drivestr, "rb+");
381
382 if (fp == NULL)
383 fp = fopen(drivestr, "wb+");
384
385 if (fp == NULL)
386 {
387 fprintf(stderr, "seldisc(): Cannot open file '%s'!\r\n",
388 drivestr);
389 return;
390 }
391
392 if (stat(drivestr, &statbuf) < 0)
393 {
394 fprintf(stderr, "seldisc(): Cannot stat file '%s'!\r\n",
395 drivestr);
396 fclose(fp);
397 return;
398 }
399
400 secs = statbuf.st_size / SECTORSIZE;
401
402 if (secs == 0)
403 {
404 char buf[SECTORSIZE];
405 memset(buf, 0xE5, SECTORSIZE);
406
407 if (fwrite(buf, 1, SECTORSIZE, fp) != SECTORSIZE)
408 {
409 fprintf(stderr, "seldisc(): Cannot create file '%s'!\r\n",
410 drivestr);
411
412 fclose(fp);
413 return;
414 }
415
416 secs = 1;
417 }
418
419 /* printf(stderr,"\r\nOpen %s on drive %d\n", drivestr, drive); */
420
421 z80->drives[drive] = fp;
422 z80->drivelen[drive] = secs * SECTORSIZE;
423 }
424 }
425
426 static void
seldisc(z80info * z80)427 seldisc(z80info *z80)
428 {
429 H = 0;
430 L = 0;
431
432 if (C >= MAXDISCS)
433 {
434 fprintf(stderr, "seldisc(): Attempt to open bogus drive %d\r\n",
435 C);
436 return;
437 }
438
439 z80->drive = C;
440
441 if (z80->drive < NUMHDISCS)
442 {
443 L = (HDPBASE + DPHSIZE * C) & 0xFF;
444 H = (HDPBASE + DPHSIZE * C) >> 8;
445 }
446 else
447 {
448 L = (DPBASE + DPHSIZE * C) & 0xFF;
449 H = (DPBASE + DPHSIZE * C) >> 8;
450 }
451
452 home(z80);
453 }
454
455 static void
settrack(z80info * z80)456 settrack(z80info *z80)
457 {
458 int tracks = (z80->drive < NUMHDISCS) ?
459 HDTRACKSPERDISC : TRACKSPERDISC;
460
461 z80->track = (B << 8) + C;
462
463 if (z80->track < RESERVEDTRACKS || z80->track >= tracks)
464 fprintf(stderr, "settrack(): bogus track %d!\r\n",
465 z80->track);
466 }
467
468 static void
setsector(z80info * z80)469 setsector(z80info *z80)
470 {
471 int sectors = (z80->drive < NUMHDISCS) ?
472 HDSECTORSPERTRACK : SECTORSPERTRACK;
473
474 z80->sector = (B << 8) + C;
475
476 if (z80->sector < SECTOROFFSET || z80->sector > sectors)
477 fprintf(stderr, "setsector(): bogus sector %d!\r\n",
478 z80->sector);
479 }
480
481 static void
setdma(z80info * z80)482 setdma(z80info *z80)
483 {
484 z80->dma = (B << 8) + C;
485 }
486
487
488 static void
rdsector(z80info * z80)489 rdsector(z80info *z80)
490 {
491 int n;
492 int drive = z80->drive;
493 int sectors = (drive < NUMHDISCS) ? HDSECTORSPERTRACK : SECTORSPERTRACK;
494 long offset = SECTORSIZE * ((long)z80->sector - SECTOROFFSET +
495 sectors * ((long)z80->track - TRACKOFFSET));
496 FILE *fp;
497 long len;
498 realizedisk(z80);
499 fp = z80->drives[drive];
500 len = z80->drivelen[drive];
501
502 if (fp == NULL)
503 {
504 fprintf(stderr, "rdsector(): file/drive %d not open!\r\n",
505 drive);
506 A = 1;
507 return;
508 }
509
510 if (len && offset >= len)
511 {
512 memset(&(z80->mem[z80->dma]), 0xE5, SECTORSIZE);
513 A = 0;
514 return;
515 }
516
517 if (fseek(fp, offset, SEEK_SET) != 0)
518 {
519 fprintf(stderr, "rdsector(): fseek failure offset=0x%lX!\r\n",
520 offset);
521 A = 1;
522 return;
523 }
524
525 n = fread(&(z80->mem[z80->dma]), 1, SECTORSIZE, fp);
526
527 if (n != SECTORSIZE)
528 {
529 fprintf(stderr, "rdsector(): read failure %d!\r\n", n);
530 A = 1;
531 }
532 else
533 A = 0;
534 }
535
536
537 static void
wrsector(z80info * z80)538 wrsector(z80info *z80)
539 {
540 int drive = z80->drive;
541 int sectors = (drive < NUMHDISCS) ? HDSECTORSPERTRACK : SECTORSPERTRACK;
542 long offset = SECTORSIZE * ((long)z80->sector - SECTOROFFSET +
543 sectors * ((long)z80->track - TRACKOFFSET));
544 FILE *fp;
545 long len;
546 realizedisk(z80);
547 fp = z80->drives[drive];
548 len = z80->drivelen[drive];
549
550 if (fp == NULL)
551 {
552 fprintf(stderr, "wrsector(): file/drive %d not open!\r\n",
553 drive);
554 A = 1;
555 return;
556 }
557
558 if (len && offset > len)
559 {
560 char buf[SECTORSIZE];
561
562 if (fseek(fp, len, SEEK_SET) != 0)
563 {
564 fprintf(stderr, "wrsector(): fseek failure offset=0x%lX!\r\n",
565 len);
566 A = 1;
567 return;
568 }
569
570 memset(buf, 0xE5, SECTORSIZE);
571
572 while (offset > len)
573 {
574 if (fwrite(buf, 1, SECTORSIZE, fp) != SECTORSIZE)
575 {
576 fprintf(stderr, "wrsector(): write failure!\r\n");
577 A = 1;
578 return;
579 }
580
581 len += SECTORSIZE;
582 z80->drivelen[drive] = len;
583 }
584 }
585
586 if (fseek(fp, offset, SEEK_SET) != 0)
587 {
588 fprintf(stderr, "wrsector(): fseek failure offset=0x%lX!\r\n",
589 offset);
590 A = 1;
591 return;
592 }
593
594 if (fwrite(&(z80->mem[z80->dma]), 1, SECTORSIZE, fp) != SECTORSIZE)
595 {
596 fprintf(stderr, "wrsector(): write failure!\r\n");
597 A = 1;
598 }
599 else
600 {
601 A = 0;
602
603 if (offset + SECTORSIZE > len)
604 z80->drivelen[drive] = offset + SECTORSIZE;
605 }
606 }
607
608 static void
secttran(z80info * z80)609 secttran(z80info *z80)
610 {
611 if (z80->drive < NUMHDISCS)
612 {
613 /* simple sector translation for hard disc */
614 HL = BC + 1;
615
616 if (BC >= HDSECTORSPERTRACK)
617 fprintf(stderr, "secttran(): bogus sector %d!\r\n", BC);
618 }
619 else
620 {
621 /* we do not need to use DE to find our translation table */
622 HL = sectorxlat[BC];
623
624 if (BC >= SECTORSPERTRACK)
625 fprintf(stderr, "secttran(): bogus sector %d!\r\n", BC);
626 }
627 }
628
629 static void
liststat(z80info * z80)630 liststat(z80info *z80)
631 {
632 A = 0xFF;
633 }
634
635 /* These two routines read and write ints at arbitrary aligned addrs.
636 * The values are stored in the z80 in little-endian order regardless
637 * of the byte-order on the host.
638 */
639 static int
addr2int(unsigned char * addr)640 addr2int(unsigned char *addr)
641 {
642 unsigned char *a = (unsigned char*)addr;
643 unsigned int t;
644
645 t = a[0] | (a[1] << 8) | (a[2] << 16) | (a[3] << 24);
646 return (int)t;
647 }
648
649 static void
int2addr(unsigned char * addr,int val)650 int2addr(unsigned char *addr, int val)
651 {
652 unsigned char *a = (unsigned char*)addr;
653 unsigned int t = (unsigned int)val;
654
655 a[0] = t & 0xFF;
656 a[1] = (t >> 8) & 0xFF;
657 a[2] = (t >> 16) & 0xFF;
658 a[3] = (t >> 24) & 0xFF;
659 }
660
661 /* Allocate file pointers - index is stored in DE */
662
663 #define CPM_FILES 4
664
665 FILE *cpm_file[CPM_FILES];
666
cpm_file_alloc(FILE * f)667 int cpm_file_alloc(FILE *f)
668 {
669 int x;
670 for (x = 0; x != CPM_FILES; ++x)
671 if (!cpm_file[x]) {
672 cpm_file[x] = f;
673 return x;
674 }
675 return -1;
676 }
677
cpm_file_get(int idx)678 FILE *cpm_file_get(int idx)
679 {
680 if (idx < 0 || idx > CPM_FILES)
681 return 0;
682 else
683 return cpm_file[idx];
684 }
685
cpm_file_free(int x)686 int cpm_file_free(int x)
687 {
688 if (x >= 0 && x < CPM_FILES && cpm_file[x]) {
689 int rtn = fclose(cpm_file[x]);
690 cpm_file[x] = 0;
691 return rtn;
692 } else {
693 return -1;
694 }
695 }
696
697 /* DE points to a CP/M FCB.
698 On return, A contains 0 if all went well, 0xFF otherwise.
699 The algorithm uses the FCB to store info about the UNIX file.
700 */
701 static void
openunix(z80info * z80)702 openunix(z80info *z80)
703 {
704 char filename[20], *fp;
705 byte *cp;
706 int i;
707 FILE *fd;
708 int fd_no;
709
710 cp = &(z80->mem[DE + 1]);
711 fp = filename;
712
713 for (i = 0; (*cp != ' ') && (i < 8); i++)
714 *fp++ = tolower(*cp++);
715
716 cp = &(z80->mem[DE + 9]);
717
718 if (*cp != ' ')
719 {
720 *fp++ = '.';
721
722 for (i = 0; (*cp != ' ') && (i < 3); i++)
723 *fp++ = tolower(*cp++);
724 }
725
726 *fp = 0;
727 A = 0xFF;
728
729 /* if file is not readable, try opening it read-only */
730 if ((fd = fopen(filename, "rb+")) == NULL)
731 if ((fd = fopen(filename, "rb")) == NULL)
732 return;
733
734 fd_no = cpm_file_alloc(fd);
735 if (fd_no != -1)
736 A = 0;
737
738 int2addr(&z80->mem[DE + FDOFFSET], fd_no);
739 int2addr(&z80->mem[DE + BLKOFFSET], 0);
740 int2addr(&z80->mem[DE + SZOFFSET], 0);
741 }
742
743
744 /* DE points to a CP/M FCB.
745 On return, A contains 0 if all went well, 0xFF otherwise.
746 The algorithm uses the FCB to store info about the UNIX file.
747 */
748 static void
createunix(z80info * z80)749 createunix(z80info *z80)
750 {
751 char filename[20], *fp;
752 byte *cp;
753 int i;
754 FILE *fd;
755 int fd_no;
756
757 cp = &(z80->mem[DE + 1]);
758 fp = filename;
759
760 for (i = 0; (*cp != ' ') && (i < 8); i++)
761 *fp++ = tolower(*cp++);
762
763 cp = &(z80->mem[DE + 9]);
764
765 if (*cp != ' ')
766 {
767 *fp++ = '.';
768
769 for (i = 0; (*cp != ' ') && (i < 3); i++)
770 *fp++ = tolower(*cp++);
771 }
772
773 *fp = 0;
774 A = 0xFF;
775
776 if ((fd = fopen(filename, "wb+")) == NULL)
777 return;
778
779 fd_no = cpm_file_alloc(fd);
780 if (fd_no != -1)
781 A = 0;
782
783 int2addr(&z80->mem[DE + FDOFFSET], fd_no);
784 int2addr(&z80->mem[DE + BLKOFFSET], 0);
785 int2addr(&z80->mem[DE + SZOFFSET], 0);
786 }
787
788
789 /* DE points to a CP/M FCB.
790 On return, A contains 0 if all went well, 0xFF otherwise.
791 The algorithm uses the FCB to store info about the UNIX file.
792 */
793 static void
rdunix(z80info * z80)794 rdunix(z80info *z80)
795 {
796 byte *cp;
797 int i, blk, size;
798 FILE *fd;
799 int fd_no;
800
801 cp = &(z80->mem[z80->dma]);
802 fd = cpm_file_get((fd_no = addr2int(&z80->mem[DE + FDOFFSET])));
803 blk = addr2int(&z80->mem[DE + BLKOFFSET]);
804 size = addr2int(&z80->mem[DE + SZOFFSET]);
805
806 A = 0xFF;
807
808 if (!fd)
809 return;
810
811 if (fseek(fd, (long)blk << 7, SEEK_SET) != 0)
812 return;
813
814 i = fread(cp, 1, SECTORSIZE, fd);
815 size = i;
816
817 if (i == 0)
818 return;
819
820 for (; i < SECTORSIZE; i++)
821 cp[i] = CNTL('Z');
822
823 A = 0;
824 blk += 1;
825
826 int2addr(&z80->mem[DE + FDOFFSET], fd_no);
827 int2addr(&z80->mem[DE + BLKOFFSET], blk);
828 int2addr(&z80->mem[DE + SZOFFSET], size);
829 }
830
831
832 /* DE points to a CP/M FCB.
833 On return, A contains 0 if all went well, 0xFF otherwise.
834 The algorithm uses the FCB to store info about the UNIX file.
835 */
836 static void
wrunix(z80info * z80)837 wrunix(z80info *z80)
838 {
839 byte *cp;
840 int i, blk, size;
841 FILE *fd;
842 int fd_no;
843
844 cp = &(z80->mem[z80->dma]);
845 fd = cpm_file_get((fd_no = addr2int(&z80->mem[DE + FDOFFSET])));
846 blk = addr2int(&z80->mem[DE + BLKOFFSET]);
847 size = addr2int(&z80->mem[DE + SZOFFSET]);
848
849 A = 0xFF;
850
851 if (fseek(fd, (long)blk << 7, SEEK_SET) != 0)
852 return;
853
854 i = fwrite(cp, 1, size = SECTORSIZE, fd);
855
856 if (i != SECTORSIZE)
857 return;
858
859 A = 0;
860 blk += 1;
861
862 int2addr(&z80->mem[DE + FDOFFSET], fd_no);
863 int2addr(&z80->mem[DE + BLKOFFSET], blk);
864 int2addr(&z80->mem[DE + SZOFFSET], size);
865 }
866
867
868 /* DE points to a CP/M FCB.
869 On return, A contains 0 if all went well, 0xFF otherwise.
870 */
871 static void
closeunix(z80info * z80)872 closeunix(z80info *z80)
873 {
874 int fd_no;
875
876 fd_no = addr2int(&z80->mem[DE + FDOFFSET]);
877 A = 0xFF;
878
879 if (cpm_file_free(fd_no))
880 return;
881
882 A = 0;
883 }
884
885 /* clean up and quit - never returns */
886 void
finish(z80info * z80)887 finish(z80info *z80)
888 {
889 resetterm();
890 exit(0);
891 }
892
893 /* Get/set the time - although only the get-time part is implemented.
894 If C==0, then get the time, else of C==0xFF, then set the time.
895 HL returns a pointer to our time table:
896 HL+0:DATE LSB SINCE 1,1,1978
897 HL+1:DATE MSB
898 HL+2:HOURS (BCD)
899 HL+3:MINUTES (BCD)
900 HL+4:SECONDS (BCD)
901 */
902 static void
dotime(z80info * z80)903 dotime(z80info *z80)
904 {
905 time_t now;
906 struct tm *t;
907 word days;
908 int y;
909
910 if (C != 0) /* do not support setting the time yet */
911 return;
912
913 time(&now);
914 t = localtime(&now);
915
916 /* days since Jan 1, 1978 + one since tm_yday starts at zero */
917 days = (t->tm_year - 78) * 365 + t->tm_yday + 1;
918
919 /* add in the number of days for the leap years - dumb but accurate */
920 for (y = 78; y < t->tm_year; y++)
921 if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
922 days++;
923
924 HL = TIMEBUF;
925 SETMEM(HL + 0, days & 0xFF);
926 SETMEM(HL + 1, days >> 8);
927 SETMEM(HL + 2, ((t->tm_hour / 10) << 4) + (t->tm_hour % 10));
928 SETMEM(HL + 3, ((t->tm_min / 10) << 4) + (t->tm_min % 10));
929 SETMEM(HL + 4, ((t->tm_sec / 10) << 4) + (t->tm_sec % 10));
930 }
931
932 void
bios(z80info * z80,int fn)933 bios(z80info *z80, int fn)
934 {
935 static void (*bioscall[])(z80info *z80) =
936 {
937 boot, /* 0 */
938 warmboot, /* 1 */
939 consstat, /* 2 */
940 consin, /* 3 */
941 consout, /* 4 */
942 list, /* 5 */
943 punch, /* 6 */
944 reader, /* 7 */
945 home, /* 8 */
946 seldisc, /* 9 */
947 settrack, /* 10 */
948 setsector, /* 11 */
949 setdma, /* 12 */
950 rdsector, /* 13 */
951 wrsector, /* 14 */
952 liststat, /* 15 */
953 secttran, /* 16 */
954 openunix, /* 17 */
955 createunix, /* 18 */
956 rdunix, /* 19 */
957 wrunix, /* 20 */
958 closeunix, /* 21 */
959 finish, /* 22 */
960 dotime /* 23 */
961 };
962
963 if (fn < 0 || fn >= sizeof bioscall / sizeof *bioscall)
964 {
965 fprintf(stderr, "Illegal BIOS call %d\r\n", fn);
966 return;
967 }
968
969 bioscall[fn](z80);
970 /* let z80 handle return */
971 }
972