1 /* yaze-ag - yet another Z80 emulator by ag.
2    Copyright (C) 1995,1998  Frank D. Cringle.
3    Modifications for CP/M 3.1 Copyright (C) 2000/2004 by Andreas Gerlich (agl)
4 
5 Yaze-ag is free software; you can redistribute it and/or modify it under
6 the terms of the GNU General Public License as published by the Free
7 Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
9 
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 /* #include <stdbool.h> */
25 
26 #include "ytypes.h"
27 
28 #include "mem_mmu.h"
29 #include "simz80.h"
30 #include "yaze.h"
31 
32 /* Z80 registers */
33 WORD af[2];			/* accumulator and flags (2 banks) */
34 int af_sel;			/* bank select for af */
35 
36 struct ddregs regs[2];		/* bc,de,hl */
37 int regs_sel;			/* bank select for ddregs */
38 
39 WORD ir;			/* other Z80 registers */
40 WORD ix;
41 WORD iy;
42 WORD sp;
43 WORD pc;
44 WORD IFF;
45 
46 /* definitiions for the brake */
47 FASTREG tStates_save;
48 FASTREG clockFrequency_save = 0;	/* default no brake */
49 FASTREG startTime_save;
50 FASTREG tStatesInSlice_save;
51 bool brakeini = true;		/* must be true! */
52 
53 /* See definitions for memory in mem_mmu.[c,h] (agl) */
54 
55 #ifndef LIBDIR
56 #define LIBDIR "/usr/local/lib/yaze"
57 #endif
58 
59 #ifdef BOOTSYS
60 static char *bootfile = "yaze-cpm3.boot";
61 #else
62 static char *bootfile = "yaze.boot";
63 #endif
64 static char *startup = ".yazerc";
65 static char *progname;
66 #ifdef BOOTSYS
67 static long loadadr = 0x100;
68 #else
69 static long loadadr = -1;
70 #endif
71 #ifdef BIOS
72 static long basepage = 0xe4;
73 static int vflag;
74 int always128 = 0;	/* is set if all drives uses 128 byte sectors	*/
75 			/* only used under CP/M 3.1			*/
76 long z3env;
77 
78 WORD	ccp_base;		/* base address of ccp */
79 WORD	bdos_base;		/* base address of bdos */
80 WORD	bios_base;		/* base address of bios */
81 WORD	bios_top;		/* end of bios, start of global work area */
82 
83 WORD	dirbuff;		/* common directory buffer for all disks */
84 WORD	dptable;		/* base of disk parameter table */
85 BYTE	*global_alv;		/* global allocation vector */
86 #endif
87 
88 extern bool rtc_avail; /* nur zum Testen */
89 
90 static void
usage(void)91 usage(void)
92 {
93     fprintf(stderr,
94             "Usage: %s {flags} {commands}\n"
95             "   -b <file>       boot from this file\n"
96             "   -l <xxxx>       load bootfile at given (hex) address\n"
97 #ifdef BIOS
98             "   -p <xx>         base page (top 2 hex digits of ccp base)\n"
99 #endif
100             "   -s <file>       startup file\n" ,progname);
101 
102 #ifdef BIOS
103     fprintf(stderr,
104 	    "   -h <n>          screen height (default 25)\n"
105 	    "   -w <n>          screen width (default 80)\n"
106             "   -v              verbose\n"
107             "   -z <xxxx>       (hex) address of z3env (if desired)\n"
108             "   -1              all drives uses 128 byte sectors (under CP/M 3.1)\n"
109             "                   (normal 2048 byte sectors are used, 128 byte\n"
110             "                   sectors are necessary to use a disk editor)\n\n");
111 #endif
112 
113     exit (1);
114 }
115 
116 /* This routine loads the bootstrap file into the simulated processor's ram.
117 
118    If a load address is given with the -l flag, the boot file is
119    loaded to that address, the z80 pc is set to it and hl is set to
120    the ccp base address.  The bootfile might be a standalone program
121    which ignores the bios vectors or it might contain a
122    self-relocating ccp/bdos (clone), in which case it should relocate
123    itself and jump to the bios cold boot vector.
124 
125    Otherwise, the boot file can contain 1 or 2 copies of cp/m.  If it
126    contains 1 copy the specified or default basepage must correspond
127    to the run-time location of the cp/m image.  If it contains 2
128    copies of the same code assembled to run at different locations,
129    the code can and will be relocated to fit the basepage in use. */
130 
131 static void
load_cpm(const char * bfname)132 load_cpm(const char *bfname)
133 {
134     FILE *cpm;
135 #ifdef BIOS
136     int i, skip = 0;
137     WORD offs;
138     struct stat	sb;
139 #endif
140 
141     if ((cpm = fopen(bfname, "r")) == NULL)
142     {
143 	char *p = xmalloc(strlen(bfname) + strlen(LIBDIR) + 1);
144 	strcpy(p, LIBDIR);
145 	strcat(p, bfname);
146 	if ((cpm = fopen(p, "r")) == NULL)
147 	    free(p);
148 	else
149 	    bfname = p;
150     }
151     if (cpm == NULL)
152     {
153 	perror(bfname);
154 	exit(1);
155     }
156     if (loadadr >= 0)
157     {
158 	fread(ram+loadadr, 1, 64*1024-loadadr, cpm);
159 	fclose(cpm);
160 	pc = loadadr;
161 #ifdef BIOS
162 	regs[regs_sel].hl = ccp_base;
163 #endif
164 	printf("Running '%s'\n", bfname);
165 	return;
166     }
167 #ifdef BIOS
168     if (fstat(fileno(cpm), &sb) != 0)
169     {
170 	perror(bfname);
171 	exit(1);
172     }
173     if ((sb.st_size >= CPM_LENGTH+0x880 && sb.st_size < 2*CPM_LENGTH) ||
174 	(sb.st_size >= 2*CPM_LENGTH+0x880))
175     {
176 	skip = 0x880;
177 	fseek(cpm, skip, SEEK_SET);
178     }
179     fread(ram + ccp_base, CPM_LENGTH, 1, cpm);
180     if (sb.st_size < 2*CPM_LENGTH) {
181 	/* single, non-relocatable copy */
182 	if (ram[ccp_base] == 0xc3 && (ram[ccp_base+2]&0xfc) != basepage)
183 	    printf("Warning: %s appears to need -p %2x\n", bfname,
184 		   ram[ccp_base+2]&0xfc);
185     }
186     else
187     {
188 	if ((sb.st_size & 1) || ram[ccp_base] != 0xc3)
189 	{
190 	    fprintf(stderr, "%s does not appear to contain 2 cp/m images\n", bfname);
191 	    exit(1);
192 	}
193 	fseek(cpm, skip + sb.st_size/2, SEEK_SET);
194 	/* this loses if the initial jump points into the 2nd half of ccp */
195 	offs = (ccp_base >> 8) - (ram[ccp_base + 2] & 0xfc);
196 	for (i = 0; i < CPM_LENGTH; i++)
197 	{
198 	    if ((BYTE) getc(cpm) != ram[ccp_base + i])
199 		ram[ccp_base + i] += offs;
200 	}
201     }
202     fclose(cpm);
203     printf("Running '%s'\n", bfname);
204     PutBYTE(3, 0x95);				/* iobyte */
205     PutBYTE(4, 0);				/* disk A: */
206     pc = bios_base;
207 #endif
208 }
209 
210 #ifdef BIOS
211 #define N_VEC	17			/* number of bios entry points */
212 
213 static void
setvectors(void)214 setvectors(void)
215 {
216     int i;
217     long cpm_brk = 64*1024;
218 
219     if (z3env == cpm_brk - 1024)	/* z3env at top of memory? */
220 	cpm_brk = z3env;
221     cpm_brk -= 128;			/* space for directory buffer */
222     dirbuff = cpm_brk;
223     cpm_brk -= 16*(16+15);		/* space for 16 disk parameter headers
224 					   and disk parameter blocks */
225     dptable = cpm_brk;
226 
227     if (basepage*256 + CPM_LENGTH + 6*N_VEC >= dptable) {
228 	fprintf(stderr, "Not enough space in simulated ram to accommodate base at %02lX\n",
229 		basepage);
230 	exit(1);
231     }
232 
233     for (i = 0; i < N_VEC; i++) { /* build bios jump table */
234 	BYTE *p = ram + bios_base + 3*i;
235 	p[0] = 0xc3;		/* jump */
236 	p[1] = 3*(i+N_VEC);
237 	p[2] = (bios_base >> 8);
238 	p[3*N_VEC] = 0x76;	/* halt */
239 	p[3*N_VEC+1] = i;	/* function code */
240 	p[3*N_VEC+2] = 0xc9;	/* return */
241     }
242     bios_top = bios_base + 6*N_VEC;
243     i = (dptable - bios_top + 7) / 8;
244     global_alv = xmalloc(i);
245     memclr(global_alv, i);
246 }
247 #endif
248 
249 /* getopt declarations */
250 extern int getopt(int argc, char * const argv[],
251 		  const char *optstring);
252 extern char *optarg;
253 extern int optind, opterr, optopt;
254 
255 
256 int
main(int argc,char ** argv)257 main(int argc, char **argv)
258 {
259     int
260 	c,
261 	gostart,
262 	term_height = 25,
263 	term_width  = 80;
264 
265 #ifdef BIOS
266     int goopt;
267 #endif
268 
269     progname = argv[0];
270 
271 /*  printf("\033]0;YAZE-AG-%s-%s%s\007",VERSION,DEVVER,BUILD); */
272     printf("\033]0;YAZE-AG-%s%s\007",VERSION,BUILD);
273 
274     puts("\nYet Another Z80 Emulator by AG, "
275 	"\033[32m"
276 
277 	"final release " VERSION
278 /*	"development v" VERSION "-" DEVVER "_beta" */
279 /*	"release candidate " VERSION "-" DEVVER    */
280 
281 	"\033[0m"
282 
283 #ifdef UCSD
284 	"/UCSD"
285 #endif
286 /*
287 #ifdef CYGWIN
288 	"\033[33m"
289 #endif
290 	BUILD
291 #ifdef CYGWIN
292 	"\033[0m"
293 #endif
294 */
295 	/* " (devel version)" */
296 #ifdef MMU
297 	" (MMU,KeyTr,WinSz,MHz)"
298 #endif
299 	"\r\n"
300 	"Copyright 1995,1998 Frank D. Cringle. "
301 	"Pagetables Copyright by Michael Haardt.\r\n"
302 	"MMU and CP/M 3.1 extensions "
303 	"Copyright (c) 2000,2019 by Andreas Gerlich.\r\n"
304 	"Keytranslation Copyright (c) 2010,2015 by Jon Saxton\r\n"
305 	"yaze-ag comes with ABSOLUTELY NO WARRANTY; for details\r\n"
306 	"see the file \"COPYING\" in the distribution directory.\r\n");
307 
308     while ((c = getopt(argc, argv, "b:l:s:h:w:"
309 #ifdef BIOS
310                                            "p:vz:1"
311 #endif
312 
313                                            "?"       )) != EOF)
314 	switch (c)
315 	{
316 	case 'b':
317 	    bootfile = optarg;
318 	    break;
319 	case 'l':
320 	    loadadr = strtol(optarg, NULL, 16);
321 	    break;
322 	case 's':
323 	    startup = optarg;
324 	    break;
325 #ifdef BIOS
326 	case 'p':
327 	    basepage = strtol(optarg, NULL, 16);
328 	    break;
329 	case 'v':
330 	    vflag = 1;
331 	    break;
332 	case 'z':
333 	    z3env = strtol(optarg, NULL, 16);
334 	    break;
335 	case '1':
336 	    always128 = 1;
337 	    break;
338 #endif
339 	case 'h':
340 	    term_height = strtol(optarg, NULL, 10);
341 	    break;
342 	case 'w':
343 	    term_width = strtol(optarg, NULL, 10);
344 	    break;
345 	case '?':
346 	    usage();
347 	}
348 
349 #ifdef MMU
350     initMEM();	/* initialice memory */
351     initMMU();  /* initialice MMU */
352 #endif
353 
354 #ifdef BIOS
355     ccp_base = basepage * 256;
356     bdos_base = ccp_base + CCP_LENGTH;
357     bios_base = bdos_base + BDOS_LENGTH;
358 
359     /* Plug screen height and width int SCB */
360     ram[bios_base - 0x100 + 0xBA] = term_height - 1;
361     ram[bios_base - 0x100 + 0xBC] = term_width - 1;
362 
363     if (z3env == bios_base)
364 	bios_base += 1024;
365 #endif
366 
367     load_cpm(bootfile);
368 #ifdef BIOS
369     setvectors();
370 
371     if (vflag)
372     {
373 	printf("bootfile:	%s\n", bootfile);
374 	if (loadadr >= 0)
375 	    printf("loadadr:	%4lx\n", loadadr);
376 	printf("startup file:	%s\n"
377 	       "basepage:	%2lx\n"
378 	       "ccp_base:	%4hx\n"
379 	       "bdos_base:	%4hx\n"
380 	       "bios_base:	%4hx\n"
381 	       "bios_top:	%4hx\n",
382 	       startup, basepage, ccp_base,
383 	       bdos_base, bios_base, bios_top);
384 	if (z3env)
385 	    printf("z3env:		%4lx\n", z3env);
386 	printf("dptable:	%4hx\n"
387 	       "dirbuf:		%4hx\n",
388 	       dptable, dirbuff);
389     }
390 #endif
391 
392     gostart = bios_init(startup);
393 
394 #ifdef BIOS
395     for (goopt = 0; !goopt && optind < argc; optind++)
396 	goopt = docmd(argv[optind]);
397 
398     if (!gostart && !goopt)
399 	monitor(0);
400 #endif
401 
402 #ifdef DEBUG
403     do
404     {
405 	FASTWORK adr = simz80(pc);
406 	if (adr & 0x10000)
407 	    monitor(adr);
408 	else
409 	    bios(ram[adr]);
410     } while (1);
411 #elif !BIOS
412     simz80(pc);
413     fprintf(stderr,"HALT\n\r");
414     fprintf(stderr,"PC   SP   IR   IX   IY   AF   BC   DE   HL   AF'  BC'  DE'  HL'\n\r");
415     fprintf(stderr,"%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x\n\r",pc,sp,ir,ix,iy,af[af_sel],regs[regs_sel].bc,regs[regs_sel].de,regs[regs_sel].hl,af[1-af_sel],regs[1-regs_sel].bc,regs[1-regs_sel].de,regs[1-regs_sel].hl);
416     exit(0);
417 #else
418     do
419     {
420 	pc = clockFrequency_save ? simz80_with_tStates(pc) : simz80(pc);
421 	bios(GetBYTE(pc));
422     } while (1);
423 #endif
424 }
425 
426 #ifndef USE_GNU_READLINE
427 void *
xmalloc(size_t size)428 xmalloc(size_t size)
429 {
430     void *p = malloc(size);
431 
432     if (p == NULL)
433     {
434 	fputs("insufficient memory\n", stderr);
435 	exit(1);
436     }
437     return p;
438 }
439 #endif
440