home *** CD-ROM | disk | FTP | other *** search
-
-
- (* Copyright 1990 by Clarence Wilkerson. All rights reserved *)
-
-
- (* This emulator has reached the endpoint of its evolution, and
- I've decided to make it available to the public in source
- code form. There are other shareware or public domain emulators,
- but none that I know of with source code. One interesting
- feature is that it is almost entirely in Turbo Pascal.
- I have not included because of its length the CCP
- assembly language code. I had at one time plans to emulate
- this in Turbo Pascal.
- *)
-
- { Things left to do
- 1) IOBYTE ok
- 2) ENHANCED CCP by offloading functions to turbo nope
- 3) TERMINAL EMULATION ok H19, check nansi code
- 4) SOFTWARE EMULATION for Z80 nope
- and support for z80 boards nope
- 5) Emulate the FCB's better ok
- 6) bootup configuration file ok
- 7) allow cp/m 3.0 emulation nope
- }
-
- program v20boot;
- uses crt,dos,printer;
- (*
- This is an CP/M 2.2 emulation program using the NEC
- v20 8080 emulation mode.
-
- Turbo Pascal 4.0-5.5 revised edition . The major design aim was to
- do this almost exclusively in Turbo Pascal rather than assembler.
-
- Structure of the source:
-
- 1) V20BOOT.PAS TP 4.0-5.5 pascal source with 99.9% of 8088 code
- 2) GOTOCPM.ASM MASM source file for routine to enter V20 8080 emulation
- mode.
- 3) V20BDOS.ASM ASM80 source for stub BDOS and BIOS in 8080 code
- 3) Not included because of size V20CCP.ASM minor changes to ZCPR v.2
- , stripping user code and submit cpability
-
- Binary files included:
- 1) V20BOOT.EXE complete compiled version
- 2) MEDLEY.ARC set of public domain CP/M programs.. LASM, MLOAD
- DISK76, SD useful for rebuilding and testing
- 3) V20BDOS.OBJ linkable form of 8080 code V20bdos
- 3) V20CCP.OBJ " "" V20CCP
- 4) GOTOCPM.OBJ " " of help routine, gotocpm used as external routine
- in Pascal source.
-
- Description:
- General Theory:
-
- The V20-30 series contains an 8080 emulation mode. The mode is entered
- by executing a "BRKEM XXX", where XXX is a software interrupt between
- 0 and $ff. Many of these interrupts on a pc are reserved for other
- functions. You may have to poke around on your fully configured system
- to find a set of 4 consecutive interrupts to be used by this program.
- I use $C0-> C4 as the default. This is changeable in the INITV20.CPM
- file.
-
- The interrupt points to a segment:offset, and the 8080 code begins
- executing at that point. The registers for the emulation have the
- obvious matches to the 8088 registers, except that 8080 sp = 8086 bp.
- Also, you must set the 8080 data segment in ds before you enter the
- BRKEM.
-
- The 8080 emulation mode has calls to execute 8088 code, via
- interrupt calls, and to return from emulation mode permanently.
- I have implemented most of the BDOS-BIOS emulation in Turbo Pascal, with
- access from the 8080 code via these interrupt calls.
-
-
- In short, this programs allocates a 8080 code segment, sets up bdos and
- bios stubs in it, and then enters emulation mode. The CCP used is a
- slightly modified ZCPR, so the look and feel is aprroximately that
- of a stock CP/M 2.2 system.
-
- The program goes to some trouble to emulate CP/M bdos calls exactly, as
- well as the console - printer oriented bios calls. One can write a
- fairly short emulator if the exactitude of bdos calls is ignored, and
- it might suffice for some purposes.
- However, there is a difference in the File
- control blocks for CP/M versus MSDOS, and several CP/M calls have no
- exact MSDOS analogue. For example, few of the first generation emulators
- will run the directory programs SD or DISK76. Often, however, assemblers
- and compilers will run under the direct translation method. On the other
- hand, this program will not run the WordStar Install program correctly,
- since it apparently tries to implement random disk access by manipulating
- reserved fields in the FCB.
-
- All BDOS functions are supported, including getalloc and get DPB,
- ( this was important for using SD and DISK76 ).
- Not many of the bios functions are supported, espcially the disk oriented
- ones. The main thing this loses for you is the direct ability to use
- CP/M diskettes, and to use DU, the disk zapper. I made up for the loss
- of CP/M disks with my READCPM utility, also written in Turbo Pascal.
- The DU loss is tougher --DU is very handy and semi-programmable.
- I find Norton's Utilities as a poor replacement in some aspects.
-
- It wouldn't take that much effort to merge my READCPM and this code,
- but that's another project.
-
- One limitation is that this V20 emulation mode does not run z-80 code.
- However, this Turbo program is written so that only a few changes
- would be required to support a memory mapped Z80 card or software 8080
- or z80 emulation. Joan Riff's Z80MU exists already, however.
-
- CP/M features missing stem from my hacking which eliminated from ZCPR
- CCP user numbers and support for SUBMIT.
- To make up for this lack of support for SUBMIT, the standard console
- functions from bdos and bios are left in place so that cp/m utilities
- such as EX can be used.
-
- Furthermore, the IOBYTE feature is implemented, and one can add new
- physical devices to supplement the ones included ( raw io and H19
- emulation ).
-
- *)
-
- const
- ESC = #27;
- CARRYFLAG = $01;
- WHICHCOMM : byte = $01; { USE COMM1 }
- DEFAULTIOBYTE : byte = $81; { CRTT, LPTT }
- BDOSSIZE :word = $600; {12 cp/m sectors} { size of disk file in cp/m sectors }
- CCPSIZE :word = $800; {16 cp/m sectors}
- NUMALLOCBYTES = $100; {bytes in 8080 bdos for bit map of allocation }
- escinflag: boolean=false;
- escoutflag: boolean=false;
- escstr: string[64] ='';
-
- { 8088 interrupts used }
- { should make this configurable }
-
- baseinterrupt : byte = $c0;
- coldb = $0; { plus baseinterrupt starting point }
- ccphandle = $1;
- bdoshandle = $2;
- bioshandle = $3; { these handles are indices into table of addresses }
-
-
- { special V-20 instructions }
-
- CALLN = $eded; { used from 8080 mode not reversed byte order }
- RETEM = $edfd; { used from 8080 mode stored in mem as ed,fd }
- BKREM = $0fff; { used from 8086 mode }
- RETI = $CF; { used from 8086 mode }
-
-
- { cpm-80 constants }
-
- { i/o byte masks }
- consolemask = $03;
- consoleshift= $0;
- readermask = $0c;
- readershift = $2;
- punchmask = $30;
- punchshift = $4;
- listmask = $c0;
- listshift = $6;
-
- { possible console values SHR 0 }
-
- TTY = 0;
- CRTT = 1; (* CRT conflict with DOS unit in TP 4.0 *)
- BAT = 2;
- UC1 = 3;
-
- { possible reader values SHR 2 }
- { TTY = 0; }
- RDR0 = 1;
- UR1 = 2;
- UR2 = 3;
-
- { possible punch values SHR 4 }
- { TTY = 0 }
- PUN = 1;
- UP1 = 2;
- UP2 = 3;
-
- { possible list values SHR 6 }
- { TTY = 0; }
- { CRT = 1; }
- LPTT = 2;
- UL1 = 3; { output to a file }
-
-
- { offsets in CPM-80 code segment }
-
- ccpaddr : word = $f200;
- bdosaddr :word = $fa00;
- biosaddr :word = $ff00;
-
- bdosdpb = $9; { + bdosaddr }
- bdosalloc= $19; { + bdosaddr }
- jmp = $c3;
- jmpwarm = $0;
- jmpbdos = $5;
- kfcb1 = $80;
- kfcb2 = $5c;
- cpmstack = $ff80; (* default value *)
- cpmoutname: string[64] ='V20OUT.LST'; { list output to a file }
- cpm80 : word = 0;
-
- type
- segment = array[0..$7ff0] of word;
- filler = array[0..$1000] of word;
-
- reg8080 = record case integer of
- 1: (a,x,l,h,c,b,e,d : byte); { 8080 mode }
- 2: (achr,u,lchr,hchr,cchr,bchr,echr,dchr: char);
- 3: (psw,hl,bc,de : word); { word mode }
- end;
- reg8080ptr = ^reg8080;
- interruptrec = record
- loc : byte;
- offset : word;
- segment : word;
- end;
-
- regpakptr =^registers;
- { cp/m and dos types }
- fcbname = array[0..11] of char;
- nstring = string[64];
- anystring = string[255];
-
- dpbblk = record case integer of
- 1: (spt : word;
- bsh : byte;
- blm : byte;
- exm : byte;
- dsm : word;
- drm : word;
- al01 : word;
- cks : word;
- off : word;
- );
- 2: (dpb : array[1..15] of byte);
- end;
-
- allocblk = record case integer of
- 1: ( allshort: array[1..16] of byte);
- 2: ( alllong: array[1..8] of word);
- end;
-
- cpmfcb = record case integer of
-
- 1: ( drive: byte;
- name : array[1..11] of char; { 1..11 }
- curext : byte; { 12 }
- rs12 : word; { 13..14 }
- recused : byte; { 15 }
- ablk : allocblk; { 15..31 }
- currec : byte; { 32 }
- randrec: word; { 33..34 }
- overflow: byte { 35 } );
- 2: (fcb : array[0..35] of byte);
- end;
-
- dosfcb = record case integer of
- 1: ( drive: byte; { 0 for default before opening, then A=1,..etc }
- name : array[1..11] of char; { 8 + 3, spaces to fill both pieces}
- curext : word;
- recsize : word; { set to 80 hex on opening }
- filesize: array[1..4] of byte;
- date : word;
- time : word;
- reserved : array[24..31] of byte;
- currec : byte;
- randrec: array[1..2] of word);
- { user should set to 0 normally }
- 2: (fcb : array[0..36] of byte);
- 3: ( cpmseqfcb: array[0..32] of byte;
- fillit : array[33..36] of byte) { for sequential records };
- 4: (cpmrandfcb: array[0..35] of byte;
- myextra : byte { for random access });
- end;
-
- dosdir = record case integer of
- 1: ( dskflg : byte;
- name : array[1..8] of char;
- ext : array[1..3] of char;
- attr : byte;
- rsvrd : array[1..10] of byte;
- time : word;
- date : word;
- strtcluster : word;
- size : array[1..4] of byte);
- 2: ( cpmdir : array[0..31] of byte);
- end;
-
- fname = array[1..11] of char;
- alloc = array[0.. NUMALLOCBYTES] of byte;
-
-
-
- { replace absolute variables in the 8080 address space by pointers }
- var myseg : word;
- mystack : word;
- myinterrupt : word;
- oldinterrupt : array[0..3] of interruptrec;
- newinterrupt : array[0..3] of interruptrec;
- cursX, cursY : byte; { h-19 emulation }
- bioschar : ^char;
- bioswhich,biosbyte : ^byte;
- cpmseg : ^segment;
- extra : ^filler;
- biosreg : reg8080ptr ;
- bdosreg : reg8080ptr ;
- ccpregs : reg8080ptr ;
- user : ^byte ; { cpm80:4;}
- iobyte : ^byte ; { cpm80:3;}
- abortflag : ^byte ; { cpm80:$ffff;} { put <> 0 to abort }
- zero : ^byte ; { cpm80:0; }
-
- { misc. variables }
-
- lastdma : word;
- ccpfile : file;
- bdosfile : file;
- cpmout : text; { for output to disk file from list }
- initf : text; { set cp/m parameters }
- z : registers;
- myownfcb : dosfcb;
- listouthandle : word;
-
- procedure v20ccp ; external ;
- {$L v20ccp.obj}
- { 8080 code stored in "v20ccp.obj" }
- { contains 8080 code ccp extracted from zcpr 2.0 }
- { with user numbers removed, and SUBMIT file removed }
- { Fancier versions could pass most functions off to Turbo routines }
- { such as assigning MSDOS paths for user numbers or drives }
- { changing directories }
-
- procedure v20bdos ; external ;
- {$L v20bdos.obj}
- { 8080 code stored in "v20bdos.obj" }
- { has cp/m 2.2 console handlers, passes rest off to routines here to }
- { imitate with msdos functions}
- { Bios is only a stub, handles character routines only, others when }
- { graceful imitation is possible}
- { fancier version could add cp/m disk imitation for a floppy}
- { but not necessary given my read cp/m }
-
-
-
- function getcurrent : byte;
- var u : registers;
- begin
- u.ah:=$19;
- msdos(u);
- getcurrent:=u.al and $0f;
- end; { getcurrent }
-
- procedure resetdisk;
- var u : registers;
- begin
- u.ah:=$0d;
- msdos(u);
- user^:=2;
- end; { resetdisk }
-
-
- procedure setjumps;
- var u : registers;
- s : ^fname;
- j : integer;
- temp: byte;
- begin
- temp:=iobyte^;
- { now intialize lowest page of memory in the cpm80 seg }
- fillchar(zero^,$ff,0);
- iobyte^:=temp;
- abortflag^:=0; { special }
- mem[cpm80:0] :=jmp;
- memw[cpm80:1]:=biosaddr+3;
- mem[cpm80:5] :=jmp;
- memw[cpm80:6]:=bdosaddr + 6; { skip serial number }
- user^:= getcurrent;
- { blank fcb's }
- s:=ptr(cpm80,$5d);
- for j:=1 to 11 do s^[j]:=' ';
- s:=ptr(cpm80,$6d);
- for j:=1 to 11 do s^[j]:=' ';
- end; { set jumps }
-
-
- procedure putnibble( x : byte);
- begin
- x:=x and $0f;
- if x < 10 then write(chr($30+x)) else write(chr(55+x));
- end;
-
- procedure putbyte( x : byte );
- begin
- x:=x and $ff;
- putnibble(x shr 4);
- putnibble(x);
- end;
-
- procedure putword( x : word);
- begin
- putbyte(hi(x));
- putbyte(lo(x));
- end;
-
- procedure putinteger( x : integer);
- begin
- putbyte(hi(x));
- putbyte(lo(x));
- end;
-
- procedure setinterrupt ( x : interruptrec);
- { set interrupt vector x to xseg:xofs }
- var z : registers;
- begin
- z.ds:= x.segment;
- z.dx:= x.offset;
- z.ah:= $25;
- z.al:= x.loc; { load ah with 25h , al with interrupt number}
- msdos(z);
- end; { setinterrupt }
-
- procedure getinterrupt ( var x : interruptrec);
- { get interrupt vector x into xseg,xofs }
- var z : registers;
- begin
- z.ah:= $35;
- z.al:= x.loc;
- msdos(z);
- x.segment:=z.es;
- x.offset :=z.bx;
- end; { getinterrupt }
-
- procedure setdefdma;
- var z :registers;
- begin
- z.ds:=cpm80;
- z.dx:=$80;
- z.ah:=$1a; { set default dma while we're at it }
- msdos(z);
- end; { setdefdma }
-
- procedure loadbdos;
- var u : ^byte;
- bbbb : ^byte;
- begin
- u :=ptr(cpm80,bdosaddr);
- bbbb:=@v20bdos;
- move(bbbb^,u^,BDOSSIZE);
- end; { load bdos }
-
-
- procedure loadccp;
- var u : ^byte;
- cccc : ^byte;
- begin
- u:= ptr(cpm80,ccpaddr);
- cccc:=@v20ccp;
- move(cccc^,u^,CCPSIZE);
- end; { move ccp }
-
- procedure warmboot( var x : registers);
- var u : registers;
- begin { reload ccp and reset dma to $80 }
- loadccp;
- resetdisk;
- setdefdma; { stack and DMA }
- lastdma:=$80;
- setjumps; { clean up 0 to $ff to ffh in cpm80 seg }
- x.bx:=ccpaddr + 3;
- end; { warmboot }
-
- procedure notimplemented;
- var ch : char;
- begin
- write(#7,#7,'Warning! Bios call ');
- putbyte(biosreg^.a);
- writeln('H not implemented.');
- writeln;
- write('Resume 8080 program or abort? <R, A> '); ch:=readkey;
- write(ch);
- if not ( upcase(ch) = 'R') then abortflag^ := $ff else writeln;
- end; { not implemented }
-
- { bios routines next }
- { cpm/80 sends parameters in c or bc, gets back in a and hl }
-
- { first are physical character output routines }
-
- { CRT device }
-
- procedure CRTOUTPUT( x : byte);
- var u : registers;
- begin
- write(chr(x));
- end; { crtoutput }
-
- procedure CRTINPUT( var x : byte);
- var ch: char;
- begin
- ch:=readkey;
- x:=ord(ch);
- end; { CRTINPUT }
-
- procedure CRTSTATUS( var x : byte);
- var u : registers;
- begin
- if keypressed then x:=$ff else x:=0;
- end; { CRTSTATUS }
-
- { TTY device }
- { use native TURBO routines }
- { emulates H-19 terminal codes }
-
- var escqueue : char;
-
- procedure ttystatus( var x : byte);
- begin
- if escinflag then x:=$ff { if have some in queue }
- else
- if keypressed then x:=$ff else x:=$0;
- end; { ttystatus }
-
-
- procedure longescape;
- var x,y : byte;
- begin
- { now take a look at longer escape sequences }
- case escstr[2] of
-
- { cursor positioning }
- 'Y' : if length(escstr) >=4 then begin
- y:=ord(escstr[3]) - 31;
- x:=ord(escstr[4]) - 31;
- gotoxy(x,y);
- escstr:='';escoutflag:=false;
- end;
-
- { various setup codes not implemented }
- 'x','y' : if length(escstr) >=3 then begin
- escstr:='';
- escoutflag:=false;
- end;
- end; { case }
- end; { long escape }
-
- procedure processescape; { try minor emulation of h-19}
- var x,y,i,j : byte;
- notproc : boolean;
- begin
- notproc:=false;
- case escstr[2] of
- 'E' : clrscr;
- 'M' : delLine;
- 'L' : InsLine;
- 'K' : ClrEol;
- 'H' : gotoxy(1,1);
- 'J' : { EOS }
- begin
- x:=whereX; y :=whereY; clreol;
- for j:=y +1 to 25 do begin
- gotoxy(1,j);clreol;
- end;
- gotoxy(x,y);
- end;
- 'p' : lowvideo;
- 'q' : NormVideo;
- 'l' : { erase entire line
- begin
- delLine; InsLine;
- end;
- 'N' : { delete char } write(' ');
- 'A' : { cursor up }
- begin
- x:=whereX; y:=whereY -1; if y < 1 then x:=1;
- gotoxy(x,y);
- end;
- 'B' : { cursor down }
- begin
- x:=whereX; y:=whereY +1; if y > 25 then y:=25;
- gotoxy(x,y);
- end;
- 'C' : { cursor right }
- begin
- x:=whereX +1; y:=whereY; if x > 80 then x:=80;
- gotoxy(x,y);
- end;
- 'D' : { cursor left }
- begin
- x:=whereX-1; y:=whereY; if y < 1 then x:=1;
- gotoxy(x,y);
- end;
- 'j' : { push cursor }
- begin
- cursX:=whereX;
- cursY:=whereY;
- end;
- 'k' : { pop cursor } gotoxy(cursX,cursY);
- 'n' : begin
- end;
- '@' : begin end; { go to insert mode }
- 'b' : begin { erase beginning of display }
- x:=whereX;y:=whereY;
- for i:=1 to y-1 do begin
- gotoxy(1,i);
- clreol;
- end;
- gotoxy(1,y);
- for i:=1 to y do write(' ');
- gotoxy(x,y);
- end;
-
- 'I' : { reverse index }
- begin
- x:=whereX; y:=whereY -1; if y < 1 then y:=1;
- gotoxy(x,y);
- end;
- 'o' : begin { erase beginning of line }
- x:=whereX;y:=whereY;
- gotoxy(1,y);
- for i:=1 to x do write(' ');
- gotoxy(x,y);
- end; { erase beginning of line }
- 'F','G','[','=','>','t','u','<','z','Z','#',']','}','{','v','w' : begin end;
- 'Y','y','x' : begin notproc:=true; longescape; end;
- end; { case }
- if not notproc then begin
- escoutflag:=false;
- escstr:='';
- exit;
- end;
-
- end; { processescape }
-
- procedure ttyoutput( x : byte);
- var s,y,yy : byte;
- j : integer;
- begin
- if x=27 then begin { start accumulating chars again }
- escoutflag:=true;
- escstr:= ''+ ESC; { start over }
- exit;
- end;
- if not escoutflag then begin
- if x <> 9 then write(chr(x)) { expand tabs }
- else begin
- s:=WhereY;y:=WhereX; yy:=y;
- y:= 9 + 8*(y div 8); if y > 80 then y:=80;
- (* do it with blanks *)
- for j:=yy to y do write(' ');
- gotoxy(y,s);
- end;
- end
- else begin
- { check for escape sequence abort }
- if x=30 then begin escoutflag:=false; escstr:='';exit;end;
- escstr:=escstr+chr(x);
- processescape;
- end;
- end; { ttyoutput }
-
- function translate( ch : char) : char;
- { translate turbo scan codes to H-19 codes }
- begin
- case ch of
- #75,#115,#178 : translate:='D';
- #77,#116,#180 : translate:='C';
- #72,#160,#175 : translate:='A';
- #80,#164,#183 : translate:='B';
- #71,#174 : translate:='H';
- #59,#84,#94,#104 : translate:='S'; { function keys 1..5 }
- #60,#85,#95,#105 : translate:='T';
- #61,#86,#96,#106 : translate:='U';
- #62,#87,#97,#107 : translate:='V';
- #63,#88,#98,#108 : translate:='W';
- #66,#91,#101,#111: translate:='P'; { blue, grey, red }
- #67,#92,#102,#112: translate:='Q';
- #68,#93,#103,#113: translate:='R';
- else translate:=ch;
- end; { case }
- end; { translate }
-
-
- procedure ttyinput( var x : byte);
- var ch: char;
- begin
- { see if any in line already }
- if escinflag then begin
- x:=ord(escqueue);
- escinflag:=false;
- exit;
- end;
- { else wait for a real character }
- ch:=readkey;
- x:=ord(ch); { return the character }
- if (ch = ESC) and keypressed then begin
- ch:=readkey;
- escinflag:=true;
- escqueue:= translate(ch); { change on the fly }
- end;
- end; { ttyinput }
-
-
- { user console 1 routines to allow external CRT on COMM1 }
- { use interrupt 14h }
-
- procedure uc1status( var x : byte);
- var u : registers;
- begin
- u.ah:=3;
- u.dx:=WHICHCOMM;
- intr($14,u);
- { status now in u.ah }
- if (u.ah and $1) <> 0 then x:=$ff else x:=$0;
- end; { uc1status }
-
- procedure uc1input( var x : byte);
- var u : registers;
- begin
- repeat uc1status(x) until x = $ff;
- u.ah:=2;
- u.dx:=whichcomm;
- intr($14,u);
- x:=u.al;
- end; { uc1input }
-
- procedure uc1output( x : byte);
- var u : registers;
- begin
- repeat
- u.ah:=3;
- u.dx:=whichcomm;
- intr($14,u);
- until u.ah and $40 <> 0;
- u.ah:=1;
- u.al:=x;
- u.dx:=whichcomm;
- intr($14,u);
- end; { uc1output }
-
-
- var nextlist : byte;
- { routed to a file, V20OUT.LST, presently }
-
- procedure ul1output( x : byte);
- var u : registers;
- begin
- u.bx:=listouthandle;
- u.ds:=dseg;
- u.dx:=ofs(nextlist); { a 1 byte buffer }
- nextlist:=x;
- u.cx:=1;
- u.ah:=$40;
- msdos(u);
- if u.flags and carryflag <> 0 then write(#7);
- if u.ax<> 1 then write('?');
- end; { ul1output }
-
-
- procedure breader( var x : byte);
- var ch : char;
- u : registers;
- begin
- case (iobyte^ and readermask) shr readershift of
- TTY : begin ch:=readkey; x:=ord(ch); end;
- RDR0 ,UR1, UR2:begin
- u.ah:=$03;
- msdos(u);
- x:=u.al;
- end;
- end; { case }
- end; { breader }
-
- procedure bpunch( x : byte);
- var u : registers;
- begin
- case (iobyte^ and punchmask) shr punchshift of
- TTY,PUN,UP1,UP2: begin
- u.ah:=$04;
- u.dl:=x;
- msdos(u);
- end;
- end;
- end; { bpunch }
-
- procedure blist( x : byte);
- begin
- case (iobyte^ and listmask) shr listshift of
- TTY: TTYOUTPUT(x);
- CRTT: crtoutput(x);
- LPTT: write(lst,chr(x));
- UL1: ul1output(x);
- end; { case }
- end; { blist }
-
- procedure blistst( var x : byte);
- begin
- x:=$0ff;
- end; { blistst }
-
-
- procedure bconst( var x : byte);
- begin
- case (iobyte^ and consolemask) of
- TTY : TTYSTATUS(x);
- CRTT: CRTSTATUS(x);
- BAT : if keypressed then x:=$ff else x:=0;
- UC1 : uc1status(x);
- end; { case }
- end;
-
- procedure bconin( var x : byte);
- var u : registers;
- ch : char;
- begin { bconin }
- case (iobyte^ and consolemask) of
- CRTT: crtinput(x);
- TTY : ttyinput(x);
- BAT : breader(x) ;
- UC1 : uc1input(x);
- end; { case }
- end; { bconin }
-
- procedure bconout( x : byte);
- begin
- case (iobyte^ and consolemask) of
- TTY : ttyoutput(x);
- CRTT : crtoutput(x);
- BAT : blist(x);
- UC1 : uc1output(x);
- end;
- end; { bconout }
-
- procedure bseldsk( var x : registers);
- var z : registers;
- begin
- z.ah:=$0e;
- z.dl:=x.cl; { restored at end }
- msdos(z);
- x.bx:=0; { no DPH for now }
- end; { bseldsk }
-
-
- procedure bsetdma(var x : registers);
- var z : registers;
- begin
- { do msdos STA }
- { DMA address in BC }
- z.ah:=$1a;
- z.dx:=x.cx; { x.dx is restored at end ; }
- lastdma:=x.cx;
- msdos(z);
- end; { bsetdma }
-
- function bsectran( x : word) : word;
- begin
- bsectran:=x;
- end; { sectran }
-
- var x : registers;
- var biostmp : byte;
-
- { next routine accessed only via CALLN BIOSHANDLE from 8080 mode }
-
- procedure biosemul;
- interrupt; (* for turbo 4.0, try built in stuff *)
- { communicates via biosreg, a has call, bc data, returns in hl }
- var x :registers;
- begin
- (* writeln('Got to BIOS. call ',biosreg^.a); *)
- x.CX:=biosreg^.bc; { pass data on }
- x.ds:=cpm80;
- case bioswhich^ of
- 4: (* bconout(x.cl);*)
- case (iobyte^ and consolemask) of
- CRTT : write(bioschar^);
- TTY : ttyoutput(biosbyte^);
- BAT : blist(biosbyte^);
- UC1 : uc1output(biosbyte^);
- end;
-
- 0: abortflag^:=1; (* cold boot should abort *)
- 1: begin
- warmboot(x);
- x.bx:=ccpaddr + 3; { jump to beginning of ccp }
- end;
- { check this for the new bdos !!! }
- 2: bconst(x.al);
- 3: bconin(x.al);
- 5: blist(x.cl);
- 6: bpunch(x.cl); { punch } { do as COM1 later }
- 7: breader(x.al); { reader } { do as COM1 later }
- 8: begin end; { home } { fake it }
- 9: bseldsk(x); { does not return DPE pointer }
- 10: notimplemented ; { settrk }
- 11: notimplemented ; { setsec }
- 12: bsetdma(x);
- 13: notimplemented ; { read }
- 14: notimplemented ; { write }
- 15: blistst( x.al);
- 16: biosreg^.hl:=bsectran(x.cx); { probably not used }
- else notimplemented;
- end; { case }
- biosreg^.a:=x.al;
- biosreg^.hl:=x.bx; (* wrong for 16 *)
- end; { bios emulation }
-
-
- { now bdos routines }
- { cpm80 sends parameters in e or de, gets back in a or hl }
-
- procedure nobdos( n : byte);
- begin
- writeln;write('BDOS call # ');putbyte(n);writeln(' not implemented.');
- writeln;
- abortflag^:=1;
- end; { nobdos }
-
-
- function getalloc : word;
- var v : ^alloc;
- u : registers;
- begin
- v:=ptr(cpm80,bdosaddr+bdosalloc);
- fillchar(v^[0],NUMALLOCBYTES,$ff); { say that it's used }
- u.dl:=0; { default drive }
- u.ah:=$36;
- msdos(u);
- if u.ax=$ffff then writeln('Invalid CP/M drive number.');
- if u.bx <> 0 then begin
- { compute number of free blocks, and mark them with a zero }
- if u.bx > (NUMALLOCBYTES shl 3) then u.bx:= NUMALLOCBYTES shl 3 ;
- v^[0]:= (not (u.bx and $0007)) and $ff; { partial block }
- fillchar(v^[$1], (u.bx shr 3 ),0); { whole blocks }
- end;
- getalloc:=bdosaddr+bdosalloc;
- end; { getalloc }
-
- { construct a fake dpb from the DOS data }
- { need this for programs like DISK76 and SD to work }
-
- function makedpb : word;
- var u : registers;
- fatid : byte;
- numalloc : word; { number of allocation blocks }
- secalloc : byte; { number of sectors per alloc block }
- physize : word; { number of bytes in physical sector }
- fake : dpbblk;
- cdpb : ^dpbblk;
- fat : ^byte;
- cpmphys : byte;
- small : boolean;
- blksize : word; { cpm alloc block size }
- begin
- cdpb:=ptr(cpm80,bdosaddr+bdosdpb);
- fillchar(cdpb^,16,0);
- with fake do begin
- u.ah:=$1b; { use newer dos call }
- msdos(u);
- fat:=ptr(u.ds,u.bx);
- fatid:=fat^; { in asm code, would have to protect DS }
-
- u.ah:=$36;u.dl:=0;
- msdos(u);
- numalloc:=u.dx; { number of allocation units }
- secalloc:=u.al; { physical sectors per allocation unit }
- physize:=u.cx; { size of physical sector }
- cpmphys:= physize shr 7; { 128 byte sectors per physical sector }
- {assume multiple of 128 }
- if fatid <> $f8 then begin
- { check for dos 1.x versus dos 2.x formats }
- if (fatid and 2) <> 0 then spt:= 8*cpmphys else spt:=9*cpmphys;
- end else begin { is harddisk }
- spt:=18*4; { 18 physical 512 byte sectors per track }
- end;
- { compute block size } { minimum of 1k for cpm }
- blksize:= ( secalloc * physize ) shr 10 ; { in 1024 chunks }
- if blksize = 0 then begin
- numalloc:=numalloc shr 1 ;
- blksize:=1;
- end; { smallest allowable cpm blocks are 1k, so adjust }
- if numalloc > 255 then small:=true else small:=false;
- { decides byte or word value for alloc blocks in cpm fcb }
- if (numalloc < 0) or ( numalloc > 8*$d0) then numalloc:=8*$d0;
- { only have $d0 bytes for allocation vector }
-
- case blksize of
- 1: begin bsh:=3; blm:=7; end;
- 2: begin bsh:=4; blm:=15; end;
- 4: begin bsh:=5; blm:=31; end;
- 8: begin bsh:=6; blm:=63; end;
- 16: begin bsh:=7; blm:=127; end;
- else writeln(#$0d,#$0a,#7,'Screwy blocksize.');
- end; { case }
- { set extent mask block }
- { 16k per extent. exm is # of extents per dir entry -1}
- { there are 16 bytes of data/entry, so if 1 byte pointers, gives}
- { 16 blocks/entry. If 2 bytes per counter, gives 8 blocks/entry }
- if small then
- exm:= blksize -1
- else exm:= (blksize shr 1) -1;
- if exm < 0 then begin
- writeln;
- writeln('Bad decode of BPB information giving invalid CP/M DPB.');
- writeln;
- end;
- dsm:=numalloc-1;
- drm:=511;
- cks:=0;
- al01:=$ff00;
- off:=0; { no reserved tracks }
- end; { with fake }
- { pass a allocation vector .. a bit map of usage of disk blocks }
- cdpb^:=fake;
- makedpb:=bdosaddr+bdosdpb;
- end; { makedpb }
-
- var
- help : dosfcb;
- w : ^dosfcb;
-
- procedure setdma; { make sure the dma address is set correctly }
- var t : registers;
- begin
- t.ah:=$1a;
- t.ds:=cpm80;
- t.dx:=lastdma;
- msdos(t);
- end; { setdma }
-
-
- { CP/M programs seemed to destroy parts of the record in fcb needed }
- { by msdos to do findnext. The following kludge is to save the }
- { result of the fcb for each findfile or findnext for use later. }
- { If different fcbs were used, this could be a problem???? }
- { However, the big problem would be a fallacious match and then }
- { a delete, so avoid this by a check on the fcb address }
- { However, this did not work.... }
- { The CCP - EMUL interaction clobbers the DMA address somewhere }
-
-
- { kludge.. set the first directory byte = 0 always }
- { because msdos puts it to be disk number a=1,..e=5 }
- { sets returned size in size bytes of fcb since some programs }
- { eg, SD, use this to size the file, instead of the BDOS CALL }
-
-
- procedure fakeit;
- var u : ^dosdir;
- y, x : longint;
- z,t : byte;
- j : word;
- begin
- { adjust directory entry returned for right size and user }
- { MSDOS returns 1st byte to be disk number. CP/M expects user number }
- { Our CP/M emulation has only userr 0, so set this to be zero, and }
- { make the extent byte and the leftover sectors byte }
-
- u:=ptr(cpm80,lastdma); { look at the directory entry returned }
-
- { read the MSDOS file size from this directory entry }
- x:= longint(u^.size[1]) + (longint(u^.size[2]) shl 8) +
- (longint(u^.size[3]) shl 16) + (longint(u^.size[4]) shl 24) ;
- if x > $400000 then x:=$400000; { set a cut off size of 4 megs }
- y:= x shr 14 ; { number of 16k extents }
- { if y > 256, in trouble, so x < 256*16k = 4 meg }
- x:= x and $3FFF; { get remainder mod 16384 }
- x:= x shr 7; { number of physical sectors }
- t:= x ;
- for j:=12 to 31 do u^.cpmdir[j]:=0; { zero the rest of it }
- u^.cpmdir[0]:=0; { set user byte = 0 }
- u^.cpmdir[$f]:=t; { sectors left over from last extent }
- u^.cpmdir[$c]:=z; { extent byte }
- end; { fakeit }
-
-
- { according to cp/m rules, a program can do no disk calls between }
- { findfirst and findnext, or between sucessive findnext calls }
- { MSDOS claims you can, but not with same FCB }
-
-
- procedure findfile(var z : registers);
- var u : ^dosfcb;
- p : ^byte;
- begin
- u:=ptr(z.ds,z.dx);
- z.ah:=$11;
- msdos(z);
- p:=ptr(cpm80,lastdma);
- if p^<> $e5 then fakeit;
- myownfcb:=u^; { save the exact fcb for use for find next }
- z.bl:=z.al; { bdos returns a in l }
- {writeln('findfile a= ',z.al); }
- end; { findfile }
-
- procedure findnext( var z : registers);
- var u : ^dosfcb;
- p : ^byte;
- begin
- u:=ptr(z.ds,z.dx);
- u^:=myownfcb; { replace it by my copy from last time }
- z.ah:=$12;
- msdos(z);
- myownfcb:=u^; { hoard it away again }
- p:=ptr(cpm80,lastdma); { see what it returned }
- if p^<> $e5 then fakeit;
- { writeln('findnext a= ',z.al); }
- z.bl:=z.al;
- end; { findnext }
-
-
- { all disk calls go directly to MSDOS bdos }
- { character calls go through 8080 BIOS and on to 8086 bios emulator }
-
-
- { adapt for interrupt servicing }
- var f : ^dosfcb;
- mine : dosfcb;
-
- { NOTES on interfacing Turbo modules as CALLN service routines
- For a Turbo routine with no parameters or local storage the
- code produced is
- push bp
- mov bp,sp
- push bp
- jump xxxx
- xxxx: ....... Turbo code
- jmp yyyy
- yyyy: mov sp,bp
- pop bp
- ret
-
- I'm trying the sequence
- push ds
- mov ds, cs:[oldds] ; at xxxx
-
- pop ds
- mov sp,bp
- pop bp ; before jump to yyyy
- iret
- }
-
-
- var spin : word;
- cpmrecord : word;
-
- procedure bdosemul; { communicates with bdosreg pointer }
- { emulate cpm80 bdos using msdos bdos }
- { problem with record lengths.. 36 versus 37 bytes }
- { so doctor the fcbs on read and write calls }
- interrupt; (* for turbo 4.0 *)
- begin
- x.ah:=bdosreg^.c; { could do this in 8080 bdos }
- { prepare for msdos call }
- x.dx:=bdosreg^.de;
- x.ds:=cpm80;
-
- case x.ah of
- { calls 0-c are handled by 8080 bdos stub via bios calls }
- { this allows for more compatibility with CP/M-80 console handling }
-
- $D : msdos(x);
- $E..$10,$13: begin
- msdos(x);
- x.bl:=x.al;
- end;
- $11: findfile(x);
- $12: findnext(x);
- $14: begin { read seq. }
- setdma;
- f:=ptr( cpm80,bdosreg^.de);
- mine:=f^; { switch fcb's for disk calls }
- x.dx:=ofs(mine);
- x.ds:=seg(mine);
- mine.recsize:=$80;
- msdos(x);
- f^.cpmseqfcb:=mine.cpmseqfcb; { identify these pieces }
- if x.al=3 then x.al:=0; { read 1 more }
- x.bl:=x.al;
- if x.al=2 then writeln(#7,'BDOS read error. DTA too small.');
- { msdos sends a 3 for partial record read. }
- { But cpm only knows about 128 byte records, so fake it }
- { one problem is that some dos programs do not terminate
- text file with $1a. should probably fake this }
- end; { read }
- $15: begin { write seq. }
- setdma;
- f:=ptr( cpm80,bdosreg^.de);
- mine:=f^; { switch fcb's for disk calls }
- mine.recsize:=$80;
- x.dx:=ofs(mine);
- x.ds:=seg(mine);
- msdos(x); x.bl:=x.al;
- f^.cpmseqfcb:=mine.cpmseqfcb; { identify these pieces }
- x.bl:=x.al;
- end;
- $16..$17: begin
- msdos(x);
- if x.al <> 0 then x.al:=$ff;
- x.bl:=x.al;
- end;
- $18: begin
- x.ah:=$19;msdos(x);
- x.ah:=$e; msdos(x);
- { now convert number in x.al to vector in x.bx}
- spin:=$ffff;
- spin:=spin shr (16 - x.al);
- x.bx:=spin;
- end; { log in vector }
- $19: begin msdos(x); x.bl:=x.al; end;
- $1a: begin { set dma address }
- lastdma:=x.dx;
- setdma;
- end;
- $1b: begin x.bx:=getalloc; x.al:=x.bl;end;
- { have to calculate .. use $1c dos call }
- $1c: begin x.al:=0;end; { set write protect..fake it }
- $1d: begin x.bx:=0; x.al:=0; end; { no write protected disks }
- $1e: begin x.bl:=0; x.al:=0; end; { set attributes }
- $1f: x.bx:=makedpb; { make it up in 8080 space }
- $20: begin { get/set user number }
- if x.dl=$ff then begin x.al:=0; x.bl:=0; end;
- end;
- $21..$22: begin { random read and write }
- writeln(#7,'Used Random READ-WRITE calls.');
- setdma;
- f:=ptr( cpm80,bdosreg^.de);
- { get the record number to save }
- mine:=f^; { switch fcb's for disk calls }
- cpmrecord:=f^.randrec[1];
- mine.randrec[2]:=0;
- x.dx:=ofs(mine);
- x.ds:=seg(mine);
- mine.recsize:=$80;
- msdos(x);
- f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
- f^.randrec[1]:= cpmrecord; { restore the record number }
- x.bl:=x.al;
- end;
- $23 : begin { file size }
- (* writeln(#7,'Used File Size DOS call.'); *)
- f:=ptr( cpm80,bdosreg^.de);
- { get the record number to save }
- mine:=f^; { switch fcb's for disk calls }
- x.dx:=ofs(mine);
- x.ds:=seg(mine);
- mine.recsize:=$80;
- msdos(x);
- f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
- x.bl:=x.al;
- end;
- $24 : begin { set random record number}
- (* writeln(lst,'Used SET RND REC'); *)
- f:=ptr( cpm80,bdosreg^.de);
- mine:=f^; { switch fcb's for disk calls }
- x.dx:=ofs(mine);
- x.ds:=seg(mine);
- mine.recsize:=$80;
- msdos(x);
- f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
- x.bl:=x.al;
- end;
- $25 : begin { selective reset disk }
- x.ah:=$0d;
- msdos(x);
- x.al:=0; x.bl:=0;
- end;
- $28: begin { zero fill random write }
- x.ah:=$22; { fake it with usual random write }
- setdma;
- f:=ptr( cpm80,bdosreg^.de);
- { get the record number to save }
- mine:=f^; { switch fcb's for disk calls }
- cpmrecord:=f^.randrec[1];
- mine.randrec[2]:=0;
- x.dx:=ofs(mine);
- x.ds:=seg(mine);
- mine.recsize:=$80;
- msdos(x);
- f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
- f^.randrec[1]:= cpmrecord; { restore the record number }
- x.bl:=x.al;
- end;
- else nobdos(x.cl);
- end; { case }
- bdosreg^.hl:=x.bx;
- bdosreg^.a:=x.al;
- end; { bdos emulation }
-
- var st : ^anystring;
- (* this later piece is not implemented yet
- the thought was to rewrite the ccp in pascal and add various features
- *)
- procedure ccpemul;
- interrupt;
- begin
- case ccpregs^.c of
- 0 : begin { chdir }
- st:=ptr(cpm80,ccpregs^.de +1);
- ChDir(st^);
- user^:=getcurrent;
- end;
- 1 : begin
- st:=ptr(cpm80,ccpregs^.de +1);
- MkDir(st^)
- end;
- 2 : begin
- st:=ptr(cpm80,ccpregs^.de +1);
- RmDir(st^)
- end;
- 3 : begin
- st:=ptr(cpm80,ccpregs^.de +1);
- user^:=getcurrent;
- GetDir(user^+1,st^)
- end;
- else begin end; { pass them through }
- end; { case }
- end; { ccp emul }
-
-
- procedure logo;
- var x : word;
- begin
- clrscr;
- writeln('CP/M-80 Emulator for NEC V-20 and the IBM-PC');
- writeln('Uses default setup or reads from file ');
- write('8080 Code Loaded at 0');putword(cpm80);writeln('H Segment');
- write('Uses interrupts ');putbyte(baseinterrupt);
- write('..');putbyte(baseinterrupt+3);writeln('H.');
- writeln;
- write('CCP loaded at ');putword(ccpaddr);
- write('H, BDOS at ') ;putword(bdosaddr);
- write('H, BIOS at ') ;putword(biosaddr);writeln('H.');
- x:=ccpaddr;
- writeln('TPA of ',x:6,' bytes.');
- write('Default IObyte is ');putbyte(defaultiobyte);writeln('H.');
- if(defaultiobyte and 1) = 0 then
- writeln('TTY: device emulates H-19 terminal.');
- writeln('UL1: device is ',cpmoutname,'.');
- writeln;
- writeln('Copyright Nov.10, 1985 by Clarence Wilkerson');
- writeln('All rights reserved.');
- writeln('Modified for Turbo Pascal 4.0, December 6, 1988');
- writeln;
- writeln;
- writeln('Exit with the BYE command, or by writing 1 in location $ffff.');
- writeln;writeln;
- end;
-
-
- procedure findcpm;
- var uu : ^byte;
- begin { put some error checking here eventually }
- new(cpmseg);
- new(extra); { hope these are together }
- cpm80:=seg(cpmseg^[0]) + (ofs(cpmseg^[0]) shr 4 ) + 1 ;
- uu:=ptr(cpm80,0);
- fillchar( uu^,$ffff,#0);
- end; { findcpm }
-
- procedure interruptset;
- var i : integer;
- begin
- for i:=0 to 3 do begin
- oldinterrupt[i].loc:=baseinterrupt + i;
- newinterrupt[i].loc:=baseinterrupt + i;
- getinterrupt(oldinterrupt[i]); { store it away for restoration }
- end;
- newinterrupt[coldb].segment :=cpm80;
- newinterrupt[coldb].offset :=biosaddr; { DO 8080 COLD BOOT ROUTINE }
- newinterrupt[ccphandle ].segment :=seg(ccpemul);
- newinterrupt[ccphandle ].offset :=ofs(ccpemul);
- newinterrupt[bdoshandle].segment :=seg(bdosemul);
- newinterrupt[bdoshandle].offset :=ofs(bdosemul);
- newinterrupt[bioshandle].segment :=seg(biosemul);
- newinterrupt[bioshandle].offset :=ofs(biosemul);
- MEM[CPM80:$FFb0]:=BASEinterrupt+COLDB;
- MEM[CPM80:$FFb1]:=BASEinterrupt+CCPHANDLE;
- MEM[CPM80:$FFb2]:=BASEinterrupt+BDOSHANDLE;
- MEM[CPM80:$FFb3]:=BASEinterrupt+BIOSHANDLE;
- for i:=0 to 3 do setinterrupt(newinterrupt[i]);
- end; { interruptsetup }
-
-
- procedure opencpmoutput;
- var u : registers;
- begin { use dos 2.0 handle calls }
- u.ah:=$3c;
- u.ds:=seg(cpmoutname);
- u.dx:=ofs(cpmoutname) + 1;
- cpmoutname:=cpmoutname + #0;
- u.cx:=0;
- msdos(u);
- listouthandle:=u.ax;
- if u.flags and carryflag <> 0 then begin
- writeln;writeln('Error on list handle opening.');
- writeln;
- end;
- end; { open cpm output }
-
- procedure closehandle( x : integer);
- var u : registers;
- begin
- u.ah:=$3e;
- u.bx:=x;
- msdos(u);
- if u.flags and carryflag <> 0 then begin
- writeln;writeln('Error on closing handle.');
- end;
- end; { closehandle }
-
- procedure restoreinterrupts;
- var i : integer;
- begin
- for i:=0 to 3 do setinterrupt(oldinterrupt[i]);
- end; { restore interrupt vectors }
-
- procedure digest(var s : anystring);
- var i,n,code : integer;
- t,tt,ttt : anystring;
- begin
- t:='';
- for i:=1 to length(s) do begin
- s[i]:=upcase(s[i]);
- if s[i] in ['A'..'Z','0'..'9','=','.','-','\',':','$'] then t:=t+s[i];
- { strip spaces and junk off }
- end; { for }
- n:=pos('=',t);
- if n=0 then exit;
- tt:=copy(t,1,n-1);
- ttt:=copy(t,n+1,255);
- if tt= 'IOBYTE' then begin
- val(ttt,n,code);
- if (code = 0) and (length(ttt) >0 ) then
- defaultiobyte:=n;
- end { get iobyte }
- else if tt= 'LIST' then begin
- if length(ttt) > 0 then cpmoutname:=ttt;
- end { get LST output file }
- else if tt= 'INTER' then begin
- val(ttt,n,code);
- if (code = 0) and (length(ttt) >0 ) then
- baseinterrupt:=n ;
- end { get beginning interrupt number }
- else if tt= 'CCP' then begin
- val(ttt,n,code);
- if (code = 0) and (length(ttt) >0 ) then
- CCPSIZE:=n * 128;
- end { length of ccp in 128 byte chunks }
- else if tt= 'ADDR' then begin
- val(ttt,n,code);
- if (code = 0) and (length(ttt) >0 ) then
- CCPADDR:=n;
- end { beginning of ccp }
-
- else if tt= 'BDOS' then begin
- val(ttt,n,code);
- if (code = 0) and (length(ttt) >0 ) then
- BDOSSIZE:=n * 128;
- end; { length of bdos-bios in 128 byte chunks }
- end; { digest }
-
- PROCEDURE getinitfile;
- var n : integer;
- parmstrng : anystring;
- { allows change of most of the setup parameters }
- begin
- assign (initf,'INITV20.CPM');
- {$I-}
- reset(initf);
- n:=IOresult;
- {$I+}
- if n <> 0 then exit; { no file }
- while not eof(initf) do begin
- readln(initf,parmstrng);
- digest(parmstrng);
- end; { while not eof }
- close(initf);
- end; { get initialization file }
-
- procedure initcpm; { set some cpm addresses }
- begin
- lastdma :=$80;
- bdosreg :=ptr(cpm80,$ffe0);
- biosreg :=ptr(cpm80,$ffd0);
- bioswhich:=ptr(cpm80,$ffd0);
- bioschar:=ptr(cpm80,$ffd4);
- biosbyte:=ptr(cpm80,$ffd4);
- ccpregs :=ptr(cpm80,$ffc0);
- user :=ptr(cpm80,4); (* user byte *)
- iobyte :=ptr(cpm80,3); (* iobyte is implemented *)
- iobyte^ :=defaultiobyte;
- abortflag:=ptr(cpm80,$ffff);
- zero :=ptr(cpm80,0);
- bdosaddr :=ccpaddr + CCPSIZE;
- end; { initcpm }
-
- procedure gotocpm(seg8080,stack8080pointer,brkemint : word); external;
- {$L gotocpm.obj}
- (* Has to be assembled with MASM or TASM
- ;
- ;
- ; procedure gotocpm(cpmseg,cpmstack,baseint : word);
- ;begin
- ; contains a kludge to get variable interrupt call
- ; or BRKEM call
- ; have to rewrite byte after the opcode
- ; on machines with a cache, could be a problem
-
- code segment word public
- assume cs:code
- public gotocpm
- gotocpm proc near
- push bp ;
- mov bp,sp ; standard set up code, no local variables
- mov ax,[bp + 4] ; brkem int # patch
- mov byte ptr cs:hack,al ; modify code
- mov ax,[bp + 8] ; using near call, so last param at bp + 4
- ; since we pushed bp to start, plus return address
- mov dx,[bp +6] ; cpmstack to dx
- push bp ; used by V20 as stack pointer
- push ds ; set equal to CP/M data=code
- mov ds,ax ; [cpm80] set data segment for cpm
- mov bp, dx ; cpmstack to set cp/m stack
- db 0fh,0ffh ; BRKEM
- hack: db 000h; ; BRKEM XX ; uses XX interrupt
- ; overwritten in this version
- pop ds
- pop bp
- mov sp,bp ; standard exit code
- pop bp
- ret 6 ; 3 two byte parameters pushed on stack
- gotocpm endp
-
- code ends
- end
-
- *)
-
- var ExitSave : pointer;
-
- procedure CPMExit;
- (* get here by disk error, etc, so cold booting CP/M would be best
- solution. However, have not done.
- *)
-
- begin
- writeln('Error in CP/M handler.');
- ErrorAddr:=Nil;
- ExitProc:=Nil;
- end;
-
- begin { main }
- GETINITFILE;
- findcpm; { get the 64k code space for cp/m-80 }
- logo;
- initcpm; { set up some cpm variables }
- setjumps; { set lower memory }
- loadccp;
- loadbdos;
- opencpmoutput;
- interruptset; { set up the interrupts }
- setdefdma; { set stack and DTA, data segment }
- iobyte^:=DEFAULTIOBYTE;
- myseg:=cpm80;
- mystack:=cpmstack;
- myinterrupt:=baseinterrupt;
- Exitsave:=ExitProc;
- ExitProc:=@CPMexit;
- (* set up segments and use BRKEM to get to emulator *)
-
- gotocpm(myseg,mystack,myinterrupt);
-
- ExitProc:=ExitSave;
- restoreinterrupts;
- closehandle(listouthandle);
- end
- .
-