; CBIOS.MAC - 2/6/85 The real BIOS for the Xerox. ; This loads itself using the SBIOS. ; It takes the place of the Xerox PROM code. ; Xerox CBIOS Version 8.0 (For MailBox/GateWay version 8.x). .z80 .xlist maclib TNC.LIB timdef cpmorgs asciictl .list true equ 0ffffh false equ not true clk4 equ false ; Set true if 4 Mhz system clock mxdrv equ 4 ; # of disk drives supported. ; What to do with the PRINTER port. ; Set ONE of these tags to true. pistnc equ true ; Second TNC, 4800 baud, 8 bit, no parity. piscon equ false ; Console, 9600 baud, 8 bit, no parity. pispr equ false ; Printer, 9600 baud, 7 bit, even parity. ; What to do with the parallel port. parlst equ false ; True to use par port for list device. ; Parallel keyboard port: ; Set keyreg to true to use a "normal" keyboard. ; Set to false and set keyxor if: ; bit 7=1 means ctl then 7fh, if bit 7=0 means ctl then ffh. keyreg equ false ; I have funny keyboards. keyxor equ 0ffh ; 7fh for Sperry, 0ffh for Honeywell kbd hdwhnd equ false ; True if hardware handshake works whirtm equ 15 ; Seconds to let disk motor whir tracks equ 77 ; # tracks on 8", use 40 for 5" stept equ 0 ; Step time for 8", use 3 for 5" secsiz equ 128 ; Size of a disk sector ; Ram Allocation ivec equ 0ff00h ; Interrupt vector page. defbuf equ 80h ; Default CPM Disk buffer area nmivec equ 66h ; Where Z80 traps on non-maskable interrupt crtpag equ 30h ; Where CRT RAM lives, in SHADOW! crtram equ 3000h ; I/O ports ; COMM port (SIO A) pu1st equ 6 pu1dat equ 4 pu1cts equ 20h pu1dcd equ 8 pu1tbe equ 4 pu1spd equ 0 ; Baud rate generator for channel A pu1buf equ bufbas ; Must be on page boundary pu1siz equ 256 ; Input buffer size ; PRINTER port (SIO B) pu2st equ 7 pu2dat equ 5 pu2cts equ 20h pu2dcd equ 8 pu2tbe equ 4 pu2spd equ 0ch ; Baud rate generator for channel B pu2buf equ pu1buf+pu1siz ; Must be on page boundary pu2siz equ 256 ; Input buffer size ; Keyboard port. pu3dat equ 1eh ; Keyboard data port of PIO pu3st equ 1fh ; Keyboard control port of PIO pu3buf equ pu2buf+pu2siz ; Must be on page boundary pu3siz equ 16 ; Must be pwr of 2 ; Misc system ports. pio.ad equ 8 ; PIO channel A, Data port pio.as equ 9 ; PIO channel A, Sts/Ctrl port pio.bd equ 0ah ; PIO channel B, Data port pio.bs equ 0bh ; PIO channel B, Sts/Ctrl port fdc.cs equ 10h ; 1771 Disc Controller: Ctrl/Status fdc.tk equ 11h ; 1771 Disc Controller: Cylinder/track fdc.se equ 12h ; 1771 Disc Controller: Sector fdc.da equ 13h ; 1771 Disc Controller: Data crt.sc equ 14h ; CRT Scroll control register ctc.c0 equ 18h ; Counter/Timer Chip Channel 0 ctc.c1 equ 19h ; Counter/Timer Chip Channel 1 ctc.c2 equ 1ah ; Counter/Timer Chip Channel 2 ctc.c3 equ 1bh ; Counter/Timer Chip Channel 3 pio.sd equ 1ch ; Data port of system PIO pio.sc equ 1dh ; Control port of system PIO ; Bit numbers for bit, res,and set instructions b0 equ 0 b1 equ 1 b2 equ 2 b3 equ 3 b4 equ 4 b5 equ 5 b6 equ 6 b7 equ 7 ; This section is the loader code. It runs as a normal .COM file. ; It sets up the interrupt vectors, then moves the BIOS ; code to upper memory where it belongs. aseg org 100h ; Initialize the I/O devices. di ; Start with interrupts off ld hl,inivio ; Set up initial I/O variables initio: ld b,(hl) ; Get count of I/O setup data bytes inc hl ld c,(hl) ; To be stuffed into this I/O port inc hl ; Point at data bytes otir ; Set up the I/O device bit b7,(hl) ; Check for end of setup blocks jr z,initio ; Loop over all I/O setups ; Set the processor to proper interrupt state. ld a,ivec shr 8 ; I/O vector page ld i,a ; Set processor for vectored interrupts im 2 ; Set up the interrupt vectors. ld hl,livec ; Point to local copy ld de,ivec ; Point to where they go ld bc,livecl ; How many bytes ldir ; Move them there ; Move the BIOS code to its execution area. ld hl,bsbgn ld de,bios ld bc,bslen ldir ei jp bios ; Go do it. ; I/O port initialization values. ; Organized for copying loop above, these are in the form: ; one byte count ; one byte port number ; n bytes of port initialization values ; followed by another block, or "FF" to mark the end of the list inivio: if parlst db 1,pio.as db a0fh ; Output mode on PIO port A db 2,pio.bs db 0cfh ; BIT mode on PIO port B db 0fh ; BITS 7..4 outputs, BITS 3..0 inputs db 1,pio.ad db 0d0h ; Strobe bit negated endif db 3,pio.sc ; PIO for system control port db 0cfh,38h,40h db 1,pio.sd db 0 db 3,pu3st ; PIO control for keyboard db 4fh db ivkbd and 0ffh ; Vector location db 83h db 1,ctc.c0 ; Chan 0 of CTC db ivctc and 0ffh db 2,ctc.c2 ; Chan 2 of CTC if clk4 db 27h,168 ; Each tick = 10.752 Ms. else db 27h,105 ; Each tick = 10.752 Ms. endif db 2,ctc.c3 ; Chan 3 of CTC db 0c7h,93 ; Counts of ch 2 = 1 sec. ; Baud rates for the SIO ports. db 1,pu1spd ; SIO port A baud rate db 12 ; 4800 baud for COMM port. db 1,pu2spd ; SIO port B baud rate if pistnc db 12 ; 4800 baud for PRINTER port. else db 14 ; 9600 baud endif ; SIO port A (COMM port). db pu1inl,pu1st ; Count, port pu1ini: db 18h ; Reset db 14h,44h ; A reg 4 - x16, 1 stop, no par db 13h,0c1h ; A reg 3 - 8 bit, rx enable db 15h,0eah ; A reg 5 - DTR, RTS, 8 bit, tx enable db 11h,19h ; A reg 1 - rx int all char, external pu1inl equ $-pu1ini ; SIO port B (PRINTER port). db pu2inl,pu2st ; Count, port pu2ini: if pispr db 18h ; Reset db 14h,43h ; B reg 4 - x16, 1 stop, even par db 13h,40h ; B reg 3 - 7 bit, no rx db 15h,0aah ; B reg 5 - DTR, RTS, 7 bit, tx enable db 12h,(ivsio and 0ffh) ; B reg 2 - Int addr db 11h,5h ; B reg 1 - rx off, ext, status affects vect else db 18h ; Reset db 14h,44h ; B reg 4 - x16, 1 stop, no par db 13h,0c1h ; B reg 3 - 8 bit, rx enable db 15h,0eah ; B reg 5 - DTR, RTS, 8 bit, tx enable db 12h,(ivsio and 0ffh) ; B reg 2 - Int addr db 11h,1dh ; B reg 1 - rx all, ext, status affects vect endif pu2inl equ $-pu2ini db 0ffh ; End of I/O initializer list ; Local copy of the interrupt vectors. ; Moved to the interrupt page at initialization time. livec: ; SIO vectors. ivsio equ ivec+($-livec) dw 0 dw pu2ext dw pu2ihd dw pu2err dw 0 dw pu1ext dw pu1ihd dw pu1err ; CTC interrupt vectors. ivctc equ ivec+($-livec) dw 0 ; CTC 0, not used. dw ctcint ; Handler for timer (1 Ms ticks) dw 0 ; CTC 2, prescale for clock. dw clkint ; Handler for clock (seconds tick) ; Keyboard PIA vectors. ivkbd equ ivec+($-livec) dw pu3ihd ; Handler for keyboard dw 0 dw 0 dw 0 livecl equ $-livec ; Here is the bios code. It is moved to its real location ; by the loader code above. bsbgn equ $ ; Start of BIOS code .phase bios biosjmp ; Console. ; pu0 is keyboard/video + PRINTER, controled by pistnc/piscon/pispr ; pu1 is COMM port (SIO A) ; pu2 is PRINTER port (SIO B) ; pu3 is keyboard/video ; pu4 is parallel port on PIO ; Console status const: ld a,(3) ; Iobyte or a jr z,pu0ist dec a jr z,pu1ist pu2ist: ld a,(pu2ict) or a jr z,pu2isu ld a,0ffh ret pu2isu: ld a,(pu2off) or a ret z di ld a,5 out (pu2st),a ld a,0eah out (pu2st),a ; Turn TNC on xor a ld (pu2off),a ei ret pu1ist: ld a,(pu1ict) or a jr z,pu1isu ld a,0ffh ret pu1isu: ld a,(pu1off) or a ret z di ld a,5 out (pu1st),a ld a,0eah out (pu1st),a ; Turn TNC on xor a ld (pu1off),a ei ret pu0ist: if piscon call pu2ist ret nz endif pu3ist: ld a,(pu3ict) ; Anything in ring buffer? or a ret z ; Return Z if not ld a,0ffh ; Yes, return non-Z and A/ FF ret ; Console input conin: ld a,(3) ; Iobyte or a jr z,pu0inp dec a jr z,pu1inp pu2inp: call pu2ist jr z,pu2inp ld hl,(pu2gpt) ld a,(hl) inc l ld (pu2gpt),hl ld hl,pu2ict dec (hl) ret pu1inp: call pu1ist jr z,pu1inp ld hl,(pu1gpt) ld a,(hl) inc l ld (pu1gpt),hl ld hl,pu1ict dec (hl) ret pu0inp: if piscon call pu2ist jr nz,pu2inp endif call pu3ist jr z,pu0inp ; Return character from keyboard. Wait if needed. pu3inp: call pu3ist ; Anything there yet? jr z,pu3inp ; Wait if not push hl ; Yes, save hl pair ld hl,pu3ict ; One less char in ring dec (hl) ld hl,pu3gpt ; Point at ring taker call pu3ibp ; Get the char pop hl ; Restore regs ret ; Return char in A ; Console output conout: ld a,(3) ; Iobyte or a jr z,pu0ot dec a jr z,pu1ot if not pistnc pu2ost: in a,(pu2st) and pu2tbe ret z ld a,0ffh ret pu2ot: call pu2ost jr z,pu2ot ld a,c out (pu2dat),a ret else pu2ot: ld a,(pu2sta) and pu2dcd ret z ; TNC is off pu2ota: in a,(pu2st) and pu2tbe jr z,pu2ota pu2otb: ld a,(pu2sta) and pu2cts jr z,pu2otb ld a,c out (pu2dat),a ret endif pu1ot: ld a,(pu1sta) and pu1dcd ret z ; TNC is off pu1ota: in a,(pu1st) and pu1tbe jr z,pu1ota pu1otb: ld a,(pu1sta) and pu1cts jr z,pu1otb ld a,c out (pu1dat),a ret pu0ot: call pu3ot if piscon jr pu2ot else ret endif if parlst pu4ot: call pu4ost ; IS IT BUSY? jr z,pu4ot ; LOOP WHILE CONSOLE STATUS=0 ld a,c ; GET CHARACTER out (pio.ad),a ; SEND CHARACTER TO PIO push af di in a,(pio.bd) res 4,a ; ASSERT STROBE LINE TO CENTRONICS out (pio.bd),a set 4,a ; DE-ASSERT STROBE out (pio.bd),a ei pop af ret pu4ost: in a,(pio.bd) ; TEST TO SEE IF BUSY xor 2 ret z ld a,0ffh ret endif ; CP/M list device. if pispr list equ pu2ot listst equ pu2ost endif if parlst list equ pu4ot listst equ pu4ost endif if (not pispr) and (not parlst) list equ pu0ot listst: ld a,0ffh ; Return always ready or a ret endif ; Punch and reader are not used. punch equ conout reader equ conin ; Increment pu3inp buffer pointer. pu3ibp: ld a,(hl) ; Pick up putter or taker low bits inc a ; Step the pointer and pu3siz-1 ; Wrap within ring ld (hl),a ; Update pointer ld hl,pu3buf ; Now find the char it points to add a,l ld l,a ld a,(hl) ; Pick up the character, for taker case ret ; The interrupt code. pu1no: ld a,(pu1off) or a ret nz ld a,5 out (pu1st),a ld (pu1off),a ld a,0e8h out (pu1st),a ret pu2no: ld a,(pu2off) or a ret nz ld a,5 out (pu2st),a ld (pu2off),a ld a,0e8h out (pu2st),a ret pu1err: ex af,af' in a,(pu1dat) ld a,30h jr pu1xx pu1ext: ex af,af' in a,(pu1st) ld (pu1sta),a ld a,10h pu1xx: out (pu1st),a jr iret2 pu1ihd: ex af,af' exx ld hl,(pu1ppt) in a,(pu1dat) ld (hl),a inc l ld (pu1ppt),hl ld hl,pu1ict inc (hl) ld a,(hl) cp pu1siz-10 call z,pu1no jr iret1 pu2err: ex af,af' in a,(pu2dat) ld a,30h jr pu2xx pu2ext: ex af,af' in a,(pu2st) ld (pu2sta),a ld a,10h pu2xx: out (pu2st),a jr iret2 pu2ihd: ex af,af' exx ld hl,(pu2ppt) in a,(pu2dat) ld (hl),a inc l ld (pu2ppt),hl ld hl,pu2ict inc (hl) ld a,(hl) cp pu2siz-10 call z,pu2no jr iret1 ; Keyboard interrupt handler. pu3ihd: exx ; Save registers ex af,af' in a,(pu3dat) ; Get the keyboard data if keyreg cpl ; Invert inverted data and 7fh ; Bit 7 = 0 else ; Fix for control chars from keyboard xor keyxor jp p,xx and 1fh endif xx: ld c,a ; Char to add to ring ld hl,pu3ict ; Ring fullness count ld a,(hl) ; Pick up count inc a ; Add one more character cp pu3siz ; Ring full? jr nc,iret1 ; Don't add another char, if full ld (hl),a ; OK, add this one ld hl,pu3ppt ; Point at ring putter call pu3ibp ; Compute address and wrap ld (hl),c ; Put char in ring iret1: exx iret2: ex af,af' ei reti pu1ppt: dw pu1buf pu1gpt: dw pu1buf pu1ict: db 0 pu1off: db 0 pu1sta: db 20h pu2ppt: dw pu2buf pu2gpt: dw pu2buf pu2ict: db 0 pu2off: db 0 pu2sta: db 20h pu3ppt: db 0 pu3gpt: db 0 pu3ict: db 0 ; Disk handler. ; Single density 8" dpb0: dw 26 ; sec/track db 3 ; bls db 7 ; blm db 0 ; exm dw 242 ; size-1 dw 63 ; dirmax db 192 ; alloc0 db 0 ; alloc1 dw 16 ; check size dw 2 ; offset ; Sector weave table for 8" single density. xlt0: db 1,7,13,19,25,5,11,17,23,3,9,15,21 db 2,8,14,20,26,6,12,18,24,4,10,16,22 ; diskdef 0,1,26,6,1024,243,64,64,2 dpe0: dw xlt0,0 dw 0,0 dw dirbuf,dpb0 dw csv0,alv0 if mxdrv gt 1 dpe1: dw xlt0,0 dw 0,0 dw dirbuf,dpb0 dw csv1,alv1 endif if mxdrv gt 2 dpe2: dw xlt0,0 dw 0,0 dw dirbuf,dpb0 dw csv2,alv2 endif if mxdrv gt 3 dpe3: dw xlt0,0 dw 0,0 dw dirbuf,dpb0 dw csv3,alv3 endif ; Sector translate sectrn: ex de,hl add hl,bc ld l,(hl) ld h,0 ret ; Select drive seldsk: ld hl,0 ; Assume bad staus ld a,c cp mxdrv ret nc ; No such drive ld (drive),a ld l,c add hl,hl add hl,hl add hl,hl add hl,hl ld de,dpe0 add hl,de ret ; Do the actual select. select: ld a,(drive) ; Get drive wanted ld hl,ldrive ; Point at last drive used cp (hl) ; Same one? ret z ; Yes, do nothing. ld (ldrive),a ; Save current inc a ; Make arg 0-3 be 1-4 ld c,a ; Save orig cp 5 ; Was it 4 or more? ret nc ; Return if so, no good. cp 3 ; Was it 2 or 3? jr c,selec1 ; Go if not inc c ; Yes, make 2 and 3 into 5 and 6 inc c ; Thus setting Side bit in b2, drives in b0-1 selec1: call pokefd ; Poke the timer, get sys PIO ld b,a ; Hold copy of sys bits and 0f8h ; Flush old drive and side bits or c ; Put in new ones call dskup1 ; Go set them in controller, wait for drive jr nz,selec2 ; Go if drive came up OK ld a,b ; Drive not happy. Restore system bits out (pio.sd),a ; As before this selection ld a,80h ; Return error indication or a ; Clear Z flag ret ; Fail return from select selec2: ld hl,fddrv ; OK, point at selected drive slot ld a,(hl) ; Get old drive cp c ; Same drive? ret z ; Yes, done ld (hl),c ; Set new drive cp 0ffh ; Was there no old one? jr z,selec3 ; If so, no need to remember track ld hl,trkvec ; Point at track slot ld e,a ld d,0 add hl,de ; for the old drive in a,(fdc.tk) ; Read old track number ld (hl),a ; Put it in vector for next use of drive selec3: ld hl,trkvec ; Now get track of new drive ld b,0 add hl,bc ld a,(hl) cp 0ffh ; If unknown, home this drive jr z,homea out (fdc.tk),a ; Else tell 1771 where the head is xor a ; Return Z, success ret ; Home the drive home: call select homea: call diskup ; Get the drive up to speed first ret z ; Return if can't xor a ; OK, set cylinder to zero ld (track),a homeb: ld b,0ch ; Set seek track 0 command call fdcsek ; Go do it xor 4 ; Set up error bits for return and 9ch ret ; Set track. settrk: ld a,c ld (track),a ret ; Set sector. setsec: ld a,c ld (sect),a ret ; Set dma address. setdma: ld (dmaadr),bc ret ; Read a sector. read: call setup call rdsec jr iod ; Write a sector. write: call setup call wtsec if not hdwhnd and not pistnc iod: push af ld c,bs call pu1ot pop af endif if not hdwhnd and pistnc iod: push af ld c,bs call pu1ot call pu2ot pop af endif ret if hdwhnd and not pistnc setup: di call pu1no ei endif if hdwhnd and pistnc setup: di call pu1no call pu2no ei endif if not hdwhnd and not pistnc setup: ld c,' ' call pu1ot endif if not hdwhnd and pistnc setup: ld c,' ' call pu1ot call pu2ot endif call select call seek ret ; ; Reload CCP + BDOS. ; ; Set up stuff in low memory for CP/M. ; loadit: ld a,0c3h ld (0),a ld (5),a ld hl,bios+3 ld (1),hl ld hl,bdos+6 ld (6),hl xor a ld (drive),a ; ld e,1 ; To force loggin of disk. call select call home ld a,cpmsec ld (sect),a ld hl,ccp ld (dmaadr),hl ld b,44 ; # sectors to read. load: push bc call rdsec pop bc dec b ret z ld de,secsiz ld hl,(dmaadr) add hl,de ld (dmaadr),hl ld a,(sect) inc a ld (sect),a sub 27 jr nz,load ld a,1 ld (sect),a ld (track),a push bc call seek pop bc jr load ; ; Do a warm boot. ; bootw: ld sp,80h call loadit ld bc,80h call setdma ld a,(4) ld c,a jp ccp+3 ; ; Finish the cold boot. ; bootc: di ld sp,80h call loadit ld bc,80h call setdma xor a ld (yr),a ; Flag for "no date entered" ei in a,(pu1st) ld (pu1sta),a in a,(pu2st) ld (pu2sta),a ; Print the id message. ld hl,idmsg idlp: ld c,(hl) inc hl ld a,c or a jp z,ccp call pu3ot jr idlp idmsg: db ff,'DOS 2.2 BIOS 8.0',0 mtrtim: ld hl,fdrunt ; Count down run time of floppy motor dec (hl) ret nz ; Return unless went to zero in a,(pio.sd) ; In which case, deselect the drive and 0f8h out (pio.sd),a ret mos: db 31,28,31,30,31,30,31,31,30,31,30,31 clkint: ld (savesp),sp ; CTC chan 3 comes here once a second ld sp,intstk ; PI stack pointer exx ex af,af' ; Tick the timer. ld hl,(timer) dec hl ld (timer),hl ld a,(sec) inc a cp 60 jr nz,tiksec ld a,(min) inc a cp 60 jr nz,tikmin ld a,(hr) inc a cp 24 jr nz,tikhr ld a,(day) ld hl,mo ld e,(hl) ld d,0 ld hl,mos add hl,de cp (hl) jr nz,tikday ; Not first day of new month ld a,(mo) cp 12 jr nz,tikmon ; Not a new year ld hl,yr inc (hl) xor a tikmon: inc a ld (mo),a xor a tikday: inc a ld (day),a xor a tikhr: ld (hr),a xor a tikmin: ld (min),a xor a tiksec: ld (sec),a call mtrtim ; Time out the disk motor ex af,af' exx ld sp,(savesp) ei reti ctcint: ld (savesp),sp ; Channel 1 of CTC comes here ld sp,intstk ; We just count down a cell for user push hl push af ld hl,(dsktmr) dec hl ld (dsktmr),hl pop af ; Restore regs and dismiss pop hl ld sp,(savesp) ei reti ; Character lookup and dispatch routine. Look for character, then get ; corresponding handler from following address table. cdisp: cpir ; Search for the character ret nz ; Return if not found add hl,bc ; Step to handler for it add hl,bc add hl,bc ld c,(hl) ; Pick up handler address into bc inc hl ld b,(hl) ret ; Return answer in bc, Z flag says OK ; Here is the CRT output routine. The 820 has an 24 by 80 CRT controller ; on the board, with the refresh RAM stashed in shadow memory at ; location 3000H. This code updates that RAM and handles some control ; sequences. pu3ot: push hl ; Save user's registers push de push bc di ; Turn off interrupts, since we are ; about to map out low memory ld (savesp),sp ; And that lets us borrow the ld sp,intstk ; interrupt level stack for this code in a,(pio.sd) ; Switch locs 0000-3FFF to shadow set b7,a ; By twiddling this bit out (pio.sd),a ld hl,crschr ; Character under the cursor ld b,(hl) ld hl,(cursor) ; Point where cursor is ld a,h ; Make sure cursor is within screen RAM and 0fh or crtpag ld h,a ld (hl),b ; Put back char under cursor call crtwrk ; Main body of pu3ot ld a,(hl) ; Get char where cursor is ld (crschr),a ; Hold copy of char under cursor cp ' ' ; Is it a space? set b7,a ; Make it inverse video jr nz,pu3oua ; If not space, just invert it ld a,(spccrs) ; Special char if cursor is on space pu3oua: ld (hl),a ; Make cursor visible ld (cursor),hl ; Remember where cursor wound up ld sp,(savesp) ; Back to caller's stack in a,(pio.sd) ; Put CRT RAM back in shadow res b7,a out (pio.sd),a ei ; Allow interrupts now pop bc ; Restore registers pop de pop hl ret crtwrk: ld de,seqflg ; In the middle of a multi char sequence? ld a,(de) or a jr nz,crtseq ; Go handle the sequence ld a,c ; Character to "type" on screen cp ' ' ; Control character? jr c,crtctl ; Yes, go dispatch on it crtsto: ld (hl),c ; Put char in screen RAM inc hl ; Use a column ld a,l ; Check for right edge and 7fh cp 80 ; Edge of screen? ret c ; No call crtcr ; Yes, wrap the cursor call crtlf ; By doing a CR LF ret ; Multi character sequences crtseq: ex de,hl ; Clear sequence flag for next time ld (hl),0 ex de,hl cp 1 ; Escape sequence? jr nz,crtsq1 ; No, try US sequence ld a,c ; Yes, what's the next char? cp '=' ; Should be equals ret nz ; If not, junk it. ld a,2 ; Say row is next ld (de),a ; in seqflg ret crtsq1: cp 2 ; Waiting for row? jr nz,crtsq2 ; Go if not ld a,3 ; Yes, say now want column ld (de),a ; in seqflg crtrs1: ld a,(scroll) ; Compute in the autoscroller add a,c ; plus desired row sub ' '-1 ; Remove 32 bias crtwl: sub 24 ; Wrap as needed jr nc,crtwl add a,24 or crtpag+crtpag ; Figure CRT RAM addr for this row ld h,a ld l,0 srl h rr l ret crtsq2: cp 3 ; Waiting for column? jr nz,crtsq3 ; No ld a,c ; Yes, get the column sub ' ' ; Remove the bias crtwc: sub 80 ; Wrap the column jr nc,crtwc add a,80 or l ; In current line ld l,a ret crtsq3: call crtsto ; Just store the char. We fouled up. ret crtctl: push hl ; Here to dispatch to control char handler ld hl,crtctb ; Point at list of known chars ld bc,ncrtcc ; Number of them call cdisp ; Look up the handler pop hl ; Restore cursor ret nz ; If not found, ignore ctl char push bc ; Call that routine ret ; Go to it. crtctb: db us,rs,esc,ff ; Control chars we handle db can,dc1,cr,em,vt,lf,ht,bs,bel ncrtcc equ $-crtctb dw crtbel ; And their handler routines dw crtbs dw crtht dw crtlf dw crtvt dw crtem dw crtcr dw crtdc1 dw crtcan dw crtff dw crtesc dw crtrs dw crtus ; RS says home cursor crtrs: ld c,' ' ; Go to row 0 (plus 32) jr crtrs1 ; Using part of ESCAPE handler ; ESCAPE char. Followed by an "=" and row and column (32 offset) crtesc: ld a,1 ; Set a one into seqflg ld (de),a ; and get the args later ret ; US says to display the next control char, not interpret it. crtus: ld a,4 ; Set marker to show next ctl char ld (de),a ; in seqflg ret ; Backspace - cursor left unless at margin crtbs: ld a,l ; Lines are 128 aligned and 7fh ; Are we at left margin? ret z ; If so, return dec hl ; Else back up one ret ; Done backspace ; EM says cursor right. crtem: ld a,l ; What column are we in now? and 7fh cp 79 ; At the edge? ret nc ; If so, don't do nothin inc hl ; OK, move right. ret ; Horiz tab. Move to next 8-col tab stop. crtht: ld de,8 ld a,l ; Stop which is at or to left of cursor and 78H add a,e ; Now the next one to the right cp 80 ; Beyond right edge? ret nc ; If so, ignore the tab ld a,l ; OK to do the tab and 0F8H ; Previous tabstop ld l,a add hl,de ; Plus eight ret ; Bell - flaps a hardware bit in case you want to build a bell crtbel: in a,(pio.sd) ; Preserve rest of the bits set b5,a ; Flap bit 5 out (pio.sd),a res b5,a ; And un-flap it out (pio.sd),a ret ; Carriage return. Back cursor up to start of current row. crtcr: ld a,l ; Get current row and 80h ; Mask it off to 0 ld l,a ; Update it ret ; That was easy ; FF says clear screen and re-init scope handler crtff: ld hl,crtram ; Cursor to top of screen push hl ; Hold while we clear it ld de,crtram+1 ; The old BLT .+1 trick ld bc,0c00h ; Whole screen ld (hl),' ' ; Clear with spaces ldir ; Zap! pop hl ; Restore cursor ld a,23 ; Reset the scroll hardware ld (scroll),a ; and our copy of it out (crt.sc),a ret ; That's it, screen cleared. ; CAN says clear to end of line crtcan: push hl ; Hold cursor ld a,l ; Get column and 7fh ld c,a ; Hold current column ld a,80 ; Clear up to col 80 sub c ; How many to do? ld b,a ; Loop counter call crtblk ; Blank a group of chars pop hl ; Restore cursor ret ; DC1 says clear to end of screen (another wonderful choice) crtdc1: call crtcan ; First clear to end of current line push hl ; Hold the cursor ld a,(scroll) ; Get scroll offset ld c,a crtxn1: ld a,l ; Figure out row number from hl rla ld a,h rla and 1FH cp c ; At end of screen? jr z,crtxn2 ; Go if so call nxtlin ; No, find next line call clrlin ; and clear it jr crtxn1 ; See if done now crtxn2: pop hl ; Restore original cursor ret ; VT says cursor up crtvt: ld de,-80h ; Back up cursor by one row add hl,de ld a,h ; Make sure still in CRT RAM cp crtpag ret nc ; Quit if not ld h,crtpag+11 ; No, wrap back to top ret ; Compute address of next line nxtlin: ld de,80h ; Step one line add hl,de ld a,h ; Off the end? cp crtpag+12 ret c ; OK if not ld h,crtpag ; Yes, back up to base ret ; Linefeed. Cursor down and maybe scroll up. crtlf: ld a,l ; Figure present line number rla ; from hl ld a,h rla and 1FH ld c,a ; Hold it call nxtlin ; Step to next line ld a,(scroll) ; Are we at bottom of screen? cp c ret nz ; If not, no need to scroll push hl ; Have to scroll. Hold cursor call clrlin ; Put up a row of spaces add hl,hl ; Shift row into H register ld a,h and 1fh ; Here's row number ld (scroll),a ; Let hardware rotate screen for us out (crt.sc),a pop hl ; Recover cursor ret clrlin: ld a,l ; Go to beginning of this line and 80h ld l,a ld b,80 ; Clear 80 columns crtblk: ld (hl),' ' ; Clear with spaces inc hl ; Step thru CRT RAM djnz crtblk ; Fill whole line ret ; Seek to requested track. seek: call diskup ; Make sure disk is spinning ret z ; It isn't. Fail. ld a,(track) ; Desired track cp tracks ; In range? ret nc ; Fail if not. out (fdc.da),a ; Tell the 1771 where to go ld b,1ch ; Seek op call fdcsek ; Plug in step rate and do it and 98h ; Check for any errors ret z ; Done if OK call homeb ; Seek failed. Try recalibrating ret nz ; That failed too? ld a,(track) ; Try the seek again if recal worked out (fdc.da),a ld b,1ch call fdcsek and 98h ; Return success or fail this time ret ; Sector Read and Write routines wtsec: call diskup ; Get the drive up to speed ret z ; Quit if can't call fdcsts ; See if disk is write locked bit b6,a ret nz ; Don't try writing if locked ld b,0a8h ; Write opcode jr rdsec1 ; Much like read from here on rdsec: call diskup ; Get the drive up to speed ret z ; Quit if can't ld b,88h ; Read opcode rdsec1: ld hl,fdop ; Save the opcode in fdop ld (hl),b ld hl,retryc ; Point at retryc ld (hl),11 ; Set initial retry count again1: di ; Turn off interrupts during transfer ld hl,nmivec ; Use NMI mechanism for the data xfer ld d,(hl) ; Hold old contents of 0066H ld (hl),0c9h ; Put a "ret" there for now ld b,secsiz ; # bytes/sector set for ini/outi ld c,fdc.da ; I/O port for read and write data ld hl,(dmaadr) ; Memory address for the sector ld a,(sect) ; Tell it what sector we want out (fdc.se),a call fdcsts ; Stop old op, get status bit b5,a ; Head loaded yet? ld a,(fdop) ; Pick up the read or write jr nz,noload ; Go if no need to load it or 4 ; Tell it to load head noload: call fdcdo ; Do the read or write bit b5,a ; Is this a read or a write? jr nz,wrwait ; Go handle write rdwait: halt ; Wait for NMI ini ; Read a byte jp nz,rdwait ; Loop if not done, JP not JR, faster call fdcskw ; Wait for it to finish CRC and 9ch ; Error bits for read jr rwwat1 ; Go clean up at end wrwait: halt ; Wait for NMI, write case outi ; Output a byte jp nz,wrwait ; Loop till sector done, JP not JR call fdcskw ; Wait for CRC to go out, get status and 0bch ; Error bits for write case. rwwat1: ld hl,nmivec ; Restore the contents of 0066H ld (hl),d ei ; Allow interrupts again ret z ; Return if no errors ld hl,retryc ; Nuts, there were errors. dec (hl) ; Count the retries jr nz,again ; If some left, try it all again or a ; Else set NZ with error bits ret ; and fail the read/write. again: call seek jr again1 ; And re-do the read or write. fdcsek: ld a,stept ; Pick up step rate bits or b ; Put in seek/home operation call fdcdo ; Hand it to the chip fdcskw: in a,(fdc.cs) ; Wait for it to finish bit b0,a jr nz,fdcskw ret fdcdo: out (fdc.cs),a ; Do the requested op in 1771 call fdcdow ; And wait a little while fdcdow: ex (sp),hl ; since 1771 is a bit slow on ex (sp),hl ; getting the status right ret fdcsts: ld a,0d0h ; Reset current op call fdcdo ; Do that and delay in a,(fdc.cs) ; See what status is ret pokefd: ld a,whirtm ; Restart motor timer at max ld (fdrunt),a call pokef1 ; A little delay in a,(pio.sd) ; Get system PIO for drive bits pokef1: ret diskup: call pokefd ; Restart timer, get drive bits and 7 ret nz ; Return if one is selected in a,(pio.sd) ; None selected. Get other bits push hl ; Save register ld hl,fddrv ; Get desired drive or (hl) ; Mix with other pio.sd bits pop hl ; Restore register dskup1: push hl ; Save two reg pairs push bc out (pio.sd),a ; Set new drive bits ld a,87h ; Tell CTC 1 to time us out (ctc.c1),a ld a,9ch ; for a while (1 Ms ticks). out (ctc.c1),a ld hl,2000 ; This many Ms. says disk doesn't turn ld (dsktmr),hl call fdcsts ; See if index bit is on and 2 ld b,a call idxwat ; Wait for index bit to change jr c,offlin ; If it didn't, give up on drive dskup2: ld hl,(dsktmr) ; It changed. Now wait for up to speed call idxwat jr c,offlin ; Go if failed call idxwat ; Wait for two half square-waves jr c,offlin ; Go if failed ld de,(dsktmr) ; Now see if it's fast enough sbc hl,de ld (idxtim),hl ; Save rotation time if clk4 ld de,420 else ld de,210 ; Fast enough? endif or a ; Clear CY for sbc sbc hl,de ; Is it this fast yet? jr nc,dskup2 ; If not, let it turn some more ld e,80h ; Fast enough.Success code. Go accept it jr oflin1 offlin: in a,(pio.sd) ; Disconnect from this drive. It's bad and 0f8h out (pio.sd),a ld e,0 ; Failure code oflin1: ld a,3 ; Through with this CTC channel di ; Because of CTC chip bug out (ctc.c1),a ei pop bc ; Restore registers pop hl ld a,e ; Success/fail code or a ; Non-Z says OK ret idxwat: call fdcsts ; Get Index hole bit and 2 xor b ; Compare to old bit jr nz,idxchg ; Go when it changes ld a,(dsktmr+1) ; See if time to give up bit b7,a jr z,idxwat ; No, keep waiting scf ; Failed. This thing isn't turning ret idxchg: ld a,b ; Aha. It changed. xor 2 ; Remember the new Index value ld b,a ret ; Return from idxwat cursor: dw crtram ; Pointer into screen RAM crschr: db 0 ; Character under the cursor spccrs: db 2 ; Character to use as cursor if on space scroll: db 0 ; Current value of scroll register seqflg: db 0 ; Flag in midst of a sequence drive: ds 1 ; Drive wanted, 0-3 ldrive: db 0ffh ; Previous drive wanted, 0-3 fddrv: db 0ffh ; Previous Drive used 1,2,5,6 track: ds 1 trkvec: db 0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh sect: ds 1 dmaadr: ds 2 fdrunt: db whirtm ; Seconds to let motor run. fdop: db 0 ; Opcode for current floppy op retryc: db 0 ; Retry count for read or write on floppy dsktmr: dw 0 ; Time counter for disk up to speed test idxtim: dw 0 ; Time for disk to turn savesp: ds 2 ds 32 intstk equ $ dirbuf: ds 128 alv0: ds 31 csv0: ds 16 if mxdrv gt 1 alv1: ds 31 csv1: ds 16 endif if mxdrv gt 2 alv2: ds 31 csv2: ds 16 endif if mxdrv gt 3 alv3: ds 31 csv3: ds 16 endif bufbas equ 256*(($ shr 8)+1) bslen equ $-bios ; Length of the bios load end