TITLE 'BDOS-BIOS EMULATOR for V20 on IBM PC' PAGE 60 ; this is bdos minus the disk calls, which are passed on to msdos ; philosophy ; handle character calls in 8080 mode so that ; can do submit and EX files ; line editing and ^S , ^C handling are easier this way ; the character i/o calls are off to the BIOS ; eventually anyway ; bdos disk calls passed off directly to 8086 MSDOS ; add selected higher calls to support ; time, date, make dir, change dir calls ; can be done in Turbo bdos-handler VER equ 2 REV equ 2 BDOSHANDLE equ 0c2h; 8086 interrupt numbers to get to 8086 mode BIOSHANDLE equ 0c3h; 80,81h used by other end to get here ; V20 macros ( used only once ) CALLN macro x ; call 8088 routine at int x db 11101101b,11101101b ; db (x and 0ffh) ; endm RETEM macro db 0edh,0fdh endm BOOTV equ 0000H; Vector to BIOS warm boot routine IOBYTE equ 0003H; System I/O device assignment TBUFF equ 0080H; Default DMA buffer location FALSE equ 0 TRUE equ 1 CR equ 0DH LF equ 0AH TAB equ 09H ; Tab BACKSP equ 08H ; Backspace CTRLC equ 03H ; Control-C CTRLE EQU 05H ; Control-E CTRLP EQU 10H ; Control-P CTRLR EQU 12H ; Control-R CTRLS EQU 13H ; Control-S CTRLU EQU 15H ; Control-U CTRLX EQU 18H ; Control-X DELDAT EQU 0E5H ; Deleted data byte ORG 0FA00H ; CSEG ; DOSLOC SERIAL: DB 0,0,0 ; Room for serial number DB 0,0,0 ENTRY: JMP BDOS ; Entry to disk monitor ; serial + 6 ;**** ;Put bdos dpb and allocation vector here so can be found easily. ; these are doctored up by the Turbo Bdos-handler DATASTART EQU $ BDOSDPB: ;serial + 9 DS 16 ; BDOSALLOC: ;serial + 25 DS 256 CTEMP1: DB 0 ; temp used for console input buffer CTEMP2: DB 0 ; temp count holder for buffer COLUMN: DB 0 ; column pointer for buffer LSTCPY: DB 0 ; list copy toggle byte CHRRDY: DB 0 ; char waiting flag OLDSP: DB 0,0 ; Caller's Stack ptr DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0 ; 48 bytes (room for stack) USRCOD: DB 0 ; user and disk code lives here CURDSK: DB 0 ; current disk holder FCB: DW 0 ; Storage for caller's parms ODATA: DW 0 ; return information storage IDATA: DB 0 OUT1: DB 0 OLDDSK: DB 0 OUTDSK: DB 0 DATAEND equ $ ds 4 ; buffer area in case miscounted ;**** ;bdos entry point. data enters as follows: ; C = command ; DE = address or 16 bit data word ; E = 8 bit data word ; return from bdos data is as follows: ; A = status or value ; HL = 16 bit value or address ;**** ;bdos entry saves input data as follows: ; DE -> FCB 0 -> OLDDSK ; E -> IDATA 0 -> OUT1 ; 0 -> ODATA sets return to BEXIT on stack ; SP -> OLDSP ; saves input data and jumps to command routine ; DESTROYS A AND HL RIGHT AWAY BDOS: LDA 0FFFFH; -1 ; test for exit ORA A JNZ BREAKOUT ; in BIOS area do a RETEM BEGIN: XCHG ; mov de to fcb and bdde SHLD BDDE SHLD FCB XCHG LXI H,BDBC mov m,c ; LXI h,0 ; zero hl SHLD ODATA ; save this for the return DAD SP ; add in the user stack pointer SHLD OLDSP ; save this for the return also LXI SP,USRCOD ; point to BDOS stack { use private stack } MOV A,E ; move data byte into A STA IDATA ; save it XRA A ; zero out a STA OLDDSK ; save this as the old disk STA OUT1 ; and at OUT1 for disk relog on exit LXI H,BEXIT ;Bexit ; get the return point address PUSH H ; and stuff it on the stack for exiting MOV A,C ; move the command into A STA BDPSW ; put it into registers at the end CPI 13 ; Max Char cmds +1 - is it too big? JNC MSDOSHANDLER ; return if so MOV C,E ; get input data from E into C LXI H,CMMNDS ; point to the dispatch table MOV E,A ; move the command from A into E MVI D,0 ; zero out D DAD D ; add in the dispatch table DAD D ; once more - over two bytes MOV E,M ; pull the jump address INX H ; into MOV D,M ; DE LHLD FCB ; get the info word into HL XCHG ; swap DE and HL PCHL ; jump to the command routine ;**** ; command dispatch table CMMNDS: DW WBOOTF ; 0: System reset DW REDCON ; 1: Console input DW WRTCON ; 2: Console output DW REDRDR ; 3: Reader input DW PUNF ; 4: Punch output DW LISTF ; 5: List output DW DIRTIO ; 6: Direct console I/O DW GETIOB ; 7: Get I/O Byte DW PUTIOB ; 8: Set I/O Byte DW PRNBUF ; 9: Print string DW REDBUF ; 10: Read console buffer DW GCSTAT ; 11: Get console status DW GETVER ; 12: Get version number ( 2.2 ) ;**** ; Read next console character CONIN: LXI H,CHRRDY ; Point to the char ready flag MOV A,M ; move the ready byte into A MVI M,0 ; and zero out the flag ORA A ; was anyone home RNZ ; return if not JMP CONIF ; otherwise go get the thing ;**** ; Read console character, echo print if it is ok CIECHO: CALL CONIN ; get a character CALL GRAFIC ; check the control characters RC ; return if the char is not printable PUSH PSW ; otherwise save it MOV C,A ; put a copy into C CALL WRTCON ; and print it out POP PSW ; restore it RET ; and return to sender ;**** ;see if A is a good control char, ret with CY set if not GRAFIC: CPI CR ; test for carriage return RZ ; return if it was CPI LF ; how about line feed RZ ; that is ok CPI TAB ; as is tab RZ ; so back with it CPI BACKSP ; Backspace in 2.0 RZ ; is known CPI ' ' ; test all other control characters RET ; and go back with carry set for no echo ;**** ; Check for console break and abort CONBRK: LDA CHRRDY ; get the character ready byte ORA A ; and check for waiting char JNZ CB1 ; non zero says something is there CALL CONSF ; check the console if none there ANI 01H ; and in the status bit RZ ; return if it came up zero CALL CONIF ; otherwise go get the waiting char CPI CTRLS ; was it a control-s JNZ CB0 ; if not return with ready flag set CALL CONIF ; if it was wait for the next char CPI CTRLC ; is this one control c? JZ BOOTV ; if so we reboot XRA A ; or else zero a RET ; and simply return CB0: STA CHRRDY ; Save input char CB1: MVI A,TRUE ; and say true to the caller RET ; and go back ;**** ; Output char in C to console, list also if LSTCPY = true CONOUT: LDA CTEMP1 ; get the first temp column counter ORA A ; is it zero? JNZ CONOU1 ; if nonzero jump over PUSH B ; otherwise save input character CALL CONBRK ; Check for abort POP B ; recover the char PUSH B ; save it again CALL CONOF ; and print the character on the console POP B ; recover once more PUSH B ; would you believe save it again LDA LSTCPY ; get the list flag byte ORA A ; to set flags CNZ LISTF ; and to list if LSTCPY=true POP B ; restore char one last time CONOU1: MOV A,C ; put the byte into A LXI H,COLUMN ; point to the buffer column counter CPI 7FH ; Rubout ? RZ ; return if so INR M ; Increment column CPI ' ' ; is the character a space? RNC ; return if less - must be control DCR M ; Decrement column MOV A,M ; and get the character there ORA A ; check for a null RZ ; return if it is MOV A,C ; move the char into C CPI BACKSP ; was it a backspace? JNZ CONOU2 ; if not jump over DCR M ; Decrement column RET ; and return CONOU2: CPI LF ; was it a linefeed? RNZ ; return if not again MVI M,0 ; Set column to 0 RET ; and return ;**** ; Print char in C at console, convert Ctrl chars to ^[char] CTLOUT: MOV A,C ; move the char into A CALL GRAFIC ; see if it is printable JNC WRTCON ; jump past cause it is ok PUSH PSW ; otherwise save ctrl char on the stack MVI C,5EH ; get the "^" char CALL CONOUT ; and print it POP PSW ; get the char back ORI 40H ; add in the bias to make it print MOV C,A ; put it into C for the next ;**** ; BDOS function 2: write to the system console char in C WRTCON: MOV A,C ; which puts it into A CPI TAB ; check for tab JNZ CONOUT ; if not jump and print TABOUT: MVI C,' ' ; Space gets printed to expand tabs CALL CONOUT ; to LDA COLUMN ; next ANI 07H ; tab JNZ TABOUT ; stop RET ; Return DELAST: CALL BACKUP ; back up one character MVI C,' ' ; then get a blank CALL CONOF ; and print it out BACKUP: MVI C,BACKSP ; get a backspace JMP CONOF ; and print it as well to move into spot ;**** ; Print pound sign, CRLF, and fix columns LBCRLF: MVI C,'#' ; get a pound sign CALL CONOUT ; and print it CALL CRLF ; Turn up a new line LB1: LDA COLUMN ; get the column counter LXI H,CTEMP2 ; point to the temp counter CMP M ; are they equal? RNC ; not there yet MVI C,' ' ; so get a space CALL CONOUT ; and print it out JMP LB1 ; loop til the counts are the same ;**** ; Print CR/LF at console CRLF: MVI C,CR ; get a cr CALL CONOUT ; and print it out MVI C,LF ; followed by a lf JMP CONOUT ; which goes out too ;**** ; Print string at (BC) until '$' with TAB expansion PRINT: LDAX B ; get the byte at (BC) CPI '$' ; is it the end mark? RZ ; if so we are done INX B ; otherwise bump the pointer PUSH B ; and save it in the stack MOV C,A ; get the character into C CALL WRTCON ; write it out expanding tabs POP B ; recover the pointer JMP PRINT ; and loop til we hit the stop ;**** ; BDOS function 10: Read console buffer at ; enter with BC -> console buffer address REDBUF: LDA COLUMN ; get the column counter into A STA CTEMP2 ; save it at CTEMP2 LHLD FCB ; get the information into HL MOV C,M ; get the buffer count byte INX H ; and bump the pointer to the next spot PUSH H ; save it for later MVI B,0 ; zero B RB0: PUSH B ; save BC for later PUSH H ; and HL as well RB1: CALL CONIN ; go get a char from the console ANI 7FH ; strip the parity bit POP H ; recover HL POP B ; and BC CPI CR ; is this character a CR? JZ RBEXIT ; if so jump over CPI LF ; how about a LF JZ RBEXIT ; jump with that as well CPI BACKSP ; do we have a backspace? JNZ CHKRUB ; if not over we go MOV A,B ; put B into A ORA A ; is it still zero? JZ RB0 ; if so go get another character DCR B ; if not decrement the count in B LDA COLUMN ; get the column counter STA CTEMP1 ; save it at CTEMP1 for later JMP RB65 ; jump over ; Check for Rubout (remove & echo last char.) CHKRUB: CPI 7FH ; do we have a rubout? JNZ CHKEOL ; if not jump over MOV A,B ; if so move b into A ORA A ; set the flags JZ RB0 ; if b was zero go get another MOV A,M ; get the char at (ODATA) DCR B ; decrement the count in B DCX H ; point back to FCB JMP RB10 ; jump to echo the char ; Check for Control-E (physical end-of-line) CHKEOL: CPI CTRLE ; is it end of line? JNZ CKPTOG ; jump over if not PUSH B ; if so save BC PUSH H ; and FCB address CALL CRLF ; Turn up a new line XRA A ; zero A STA CTEMP2 ; set it into CTEMP2 JMP RB1 ; and go get more ; Check for Control-P CKPTOG: CPI CTRLP ; is it the print toggle JNZ CKBOL ; if not jump past PUSH H ; if so save FCB address LXI H,LSTCPY ; get the pointer to print toggle byte MVI A,01H ; put a 1 into A SUB M ; subtract it from the toggle MOV M,A ; and put it back POP H ; recover FCB JMP RB0 ; and go get more ; Check for Control-X (bacK space to beg. current line) CKBOL: CPI CTRLX ; do we back up? JNZ CKREML ; if not on to the next choice POP H ; if so restore the stack pointer BLOOP: LDA CTEMP2 ; get the byte at CTEMP2 LXI H,COLUMN ; and point to the column counter CMP M ; are they the same? JNC REDBUF ; if so go try again for input DCR M ; if not decrement the CTEMP2 count CALL DELAST ; delete the character there JMP BLOOP ; and loop until we are done ; check for control-U CKREML: CPI CTRLU ; do we remove the line after newline? JNZ CKRETL ; if not try again CALL LBCRLF ; if so print a "#" and CR POP H ; restore the stack JMP REDBUF ; and try for input again ; Check for Control-R (retype current line after new line) CKRETL: CPI CTRLR ; want to retype? JNZ ECHOCC ; if not onward for next RB65: PUSH B ; if so save the count in B CALL LBCRLF ; print a "#" and CRLF POP B ; recover ODATA POP H ; and FCB PUSH H ; saving FCB again PUSH B ; and ODATA RB7: MOV A,B ; move the count into ORA A ; see if it is zero JZ FIXCOL ; if so jump over INX H ; if not point to ODATA MOV C,M ; pull the byte there into C DCR B ; decrement the count PUSH B ; save it on the stack PUSH H ; and the ODATA pointer CALL CTLOUT ; print it out expanding control chars POP H ; recover the ODATA pointer POP B ; and the count in B JMP RB7 ; loop until the line is out ;fix up the column counters FIXCOL: PUSH H ; save the pointer LDA CTEMP1 ; get CTEMP1 ORA A ; set the flags JZ RB1 ; if zero go get the next character LXI H,COLUMN ; point to the column counter SUB M ; subtract it from the value of CTEMP1 STA CTEMP1 ; and save this back in CTEMP1 FXLOOP: CALL DELAST ; delete the last character LXI H,CTEMP1 ; point to CTEMP1 DCR M ; decrement it by one JNZ FXLOOP ; loop to delete all of them JMP RB1 ; go get the next character ;echo the control character ECHOCC: INX H ; must be some other control character MOV M,A ; put the character into ODATA+1 INR B ; bump the count by one RB10: PUSH B ; save it on the stack PUSH H ; and the pointer as well MOV C,A ; put the character into C CALL CTLOUT ; print it out with grafic control chars POP H ; recover the pointer POP B ; and the count MOV A,M ; put the byte at (HL) into A CPI CTRLC ; is it an abort? MOV A,B ; put the count into A JNZ RB11 ; if no abort jump over CPI 01H ; is the count 1? JZ BOOTV ; if so boot RB11: CMP C ; if not does it equal C JC RB0 ; if less go get another char RBEXIT: POP H ; recover the pointer MOV M,B ; put the count in b there MVI C,CR ; get a CR JMP CONOUT ; and print it out ;**** ;BDOS function 1: Read console - return with byte in A REDCON: CALL CIECHO ; get a char echo if printable JMP GOBAK ; and go back with it ;**** ; BDOS function 3: Read reader - return with byte in A REDRDR: CALL READF ; get byte from reader JMP GOBAK ; and return with it ;**** ; BDOS function 6: Direct I/O ; on entry, C=FF for input, C=char for output ; (Book says E reg vice C) ; appears can enter with FE or FF --- ???? ; return with char or status in A DIRTIO: MOV A,C ; Get request INR A ; Test for FF=input request JZ INREQ ; Skip down if input request INR A ; if FF adding one will set zero flag JZ CONSF ; if it was go get console status JMP CONOF ; otherwise go send it out INREQ: CALL CONSF ; get console status ORA A ; set flags JZ REXIT ; return if none - restore first CALL CONIF ; if someone is there go get it JMP GOBAK ; and return with it ;**** ; BDOS function 7: get IO byte into A GETIOB: LDA IOBYTE ; get the iobyte JMP GOBAK ; and go back with it ;**** ; BDOS function 8: set IO byte from C into place PUTIOB: LXI H,IOBYTE ; point to the iobyte MOV M,C ; put the new value in from C RET ; and return ;**** ; BDOS function 9: Print console buffer until '$' ; entry string address in DE PRNBUF: XCHG ; swap DE and HL - HL points to buffer MOV C,L ; and get a copy MOV B,H ; of HL into BC JMP PRINT ; go to the print routine ;**** ; BDOS function 11: check console status - return in A GCSTAT: CALL CONBRK ; Check for abort GOBAK: STA ODATA ; save result in return info byte JR: RET ; and go back to caller JR1: MVI A,01H ; get a 1 for return JMP GOBAK ; set it in to rinfo and return ;**** ; BDOS function 12: return version number GETVER: MVI A,22H JMP GOBAK ;**** ; bdos exit routine BEXIT: LDA OUT1 ; get the out flag for drive reset ORA A ; set the flags JZ REXIT ; if zero we may exit as is LHLD FCB ; otherwise get the FCB address MVI M,0 ; put a zero into drive select byte LDA OLDDSK ; get the old disk out ORA A ; was it zero JZ REXIT ; if so exit MOV M,A ; otherwise set in the old disk byte LDA OUTDSK ; get the disk to relog STA IDATA ; save it for the login CALL LOGIN ; go log in the old disk REXIT: LHLD OLDSP ; get the old stack pointer out SPHL ; put it back in place LHLD ODATA ; get the output data into place MOV A,L ; put L into A MOV B,H ; and H into B RET ; then return from BDOS happy ;**** ; hand off all higher calls to 8086 handler ; MSDOSHANDLER: lhld fcb ; recover de mov d,h mov e,l mov c,a ; put call back calln bdoshandle ; special v20 call BDOSCALL EQU $-1 lhld bdhl ; get return shld odata ; should return in hl and a jmp Bexit LOGIN: lxi h,bdbc mvi m,0eh ; select the disk in a lxi h,bdpsw mov m,a CALLN BDOSHANDLE LOGCALL EQU $-1 ret ; put data at the rear ;**** ; console input buffer data storage area ; move part of bios here COLDBOOT: ; should load addresses in lower memory jmp0: ; MOVE INTERRUPT LOCATIONS INTO PLACE ; FOR BDOS AND BIOS LDA BIOSHAN STA CMMNCALL ; COMMON BIOS CALL HANDLER STA WMNCALL ; WARMBOOT CALL HANDLER LDA BDOSHAN STA LOGCALL STA BDOSCALL ; AND DO WARM BOOT jmp1: ; warm boot ; zero out data areas in bdos also lxi h,datastart lxi b,dataend-datastart luuu: mov a,b ora c jz doneit xra a mov m,a inx h dcx b jmp luuu doneit: mvi a,1 jmp warmer jmp2: mvi a,2 jmp commonbios jmp3: mvi a,3 jmp commonbios jmp4: mvi a,4 jmp commonbios jmp5: mvi a,5 jmp commonbios jmp6: mvi a,6 jmp commonbios jmp7: mvi a,7 jmp commonbios jmp8: mvi a,8 jmp commonbios jmp9: mvi a,9 jmp commonbios jmp10: mvi a,10 jmp commonbios jmp11: mvi a,11 jmp commonbios jmp12: mvi a,12 jmp commonbios jmp13: mvi a,13 jmp commonbios jmp14: mvi a,14 jmp commonbios jmp15: mvi a,15 jmp commonbios jmp16: mvi a,16 jmp commonbios FILLEND EQU $ ORG SERIAL + 500H; BIOS: COLD: jmp coldboot WBOOTF: jmp jmp1 ; do dos call to load the ccp again ; under CP/M-86, could reinitialize BDOS CONSF: jmp jmp2 ; CONSOLE STATUS CONIF: jmp jmp3 ; conin CONOF: jmp jmp4 ; Conout LISTF: jmp jmp5 ; list PUNF: jmp jmp6 ; punch READF: jmp jmp7 ; reader ;the following (except for listst) are not implemented ;and will warm boot the CP/M-80 system HOMF: jmp jmp8 ; notimpl; home SELF: jmp jmp9 ; notimpl; seldsk TRKF: jmp jmp10 ; notimpl; settrk SECF: jmp jmp11 ; notimpl; setdsk DMAF: jmp jmp12 ; setdma DRDF: jmp jmp13 ; notimpl; read DWRF: jmp jmp14 ; notimpl; write LISTS: jmp jmp15 ; listst ; SECTRN:jmp jmp16 ; sectran ; pass all bios calls onto the turbo handler ; wipes out a,bc,hl breakout: ; return permanently to 8086 mode where we left with BRKEM RETEM ; to whence it came commonbios: ; call 8088 code sta bpsw ; show call lda abortflag ; TEST FOR ABORT ora a jnz breakout ; most cp/m bios routines only use bc ; disk routines may use more, but not implemented here mov h,b mov l,c shld bbc CALLN BIOSHANDLE ; go to turbo handler and do it cmmncall EQU $-1 lhld bhl ; bios returns in a and hl lda bpsw ret ; this one not used usually warmer: sta bpsw ; show call lda abortflag ; TEST FOR ABORT ora a jnz breakout CALLN BIOSHANDLE WMNCALL EQU $-1 LXI SP,CPMSTACK LHLD BHL ; get the address PCHL ; and jump codeend equ $ org 0ff80h; cpmstack: dw 0 ORG 0FFB0H COLDHAN: DB 80H CCPHAN: DB 81H BDOSHAN: DB 82H BIOSHAN: DB 83H org 0ffc0h; ccpreg: ccppsw: dw 0 ;ax ccphl: dw 0 ;bx ccpbc: dw 0 ;cx ccpde: dw 0 ;dx org 0ffd0h bpsw: dw 0 ;ax bhl: dw 0 ;bx bbc: dw 0 ;cx bde: dw 0 ;dx org 0ffe0h BDOSreg: bdpsw: dw 0 ;ax bdhl: dw 0 ;bx bdbc: dw 0 ;cx bdde: dw 0 ;dx org 0ffffh; abortflag: db 0; non-zero aborts biosend EQU $ end ; stub bdos-bios