;****************************************************************************** ; ; PC2Flop - Transfer disk image from PC to floppy over serial port. ; ; This program writes a Northstar single density disk from a disk image ; transmitted from a PC. The image is transmitted through a Sol-20 ; port using the XMODEM checksum protocol. The image is written ; directly to the floppy in raw format (256 bytes per sector, 10 ; sectors per track, 35 tracks). The disk image is the linear sequence ; of 256 byte sectors from track 0, sector 0 through track 34, sector 9. ; ; This program is for the original single density controller from ; Northstar. A different version of PC2Flop is required for the ; newer double density controller. ; ; This program runs standalone at 0x100 or under CP/M. The program ; is exited with a CTRL-C and either cold boots CP/M or exits to ; the SOLOS/CUTER ROM. ; ; Written by Mike Douglas with some Northstar disk driver code from ; Martin Eberhard ; ; Rev Date Desc ; 1.0 11/14/14 Original ; ; 1.2 11/19/14 Use generic algorithms for buffering disk ; and XMODEM I/O to handle sector sizes that ; aren't multiples of the 128 byte XMODEM ; packet. Not really needed for the Northstar, ; but it makes this code more portable. Change ; drive validation, console I/O, serial I/O, ; etc. to make as common an interface as ; possible for different platforms. ; 1.3 12/2/14 Ctrl-C during transfer returns to main menu ; instead of exiting. CR required after answering ; drive and port prompts. Check for 5 jumps of ; a jump table in chkCpm. ; ;***************************************************************************** ; CUTER/SOLOS ROM/RAM locations CUTER equ 0C000h ;CUTER RETRN equ CUTER+004h ;Warm boot, jumps to COMND ; Disk information equates NUMTRK equ 35 ;number of tracks NUMSEC equ 10 ;number of sectors per track SECLEN equ 256 ;sector length (as transmitted) MINDRV equ 1 ;first drive number MAXDRV equ 3 ;max drive number TRKLEN equ (NUMSEC*SECLEN) ;length of track in bytes RDTRIES equ 5 ;number of read (verify) attempts WRTRIES equ 4 ;number of write attempts ; Monitor, CP/M boot entry points MONITOR equ RETRN ;ROM monitor entry point (stand alone mode) CPMBOOT set NSBOOT ;cold boot ROM entry point ; XMODEM equates PKTLEN equ 128 ;128 byte xmodem packet length SOH equ 001h EOT equ 004h ACK equ 006h NAK equ 015h EOF equ 01ah ;ctrl-z character ; CP/M Equates WBOOT equ 0 ;warm boot jump location ; Misc equates CR equ 13 ;ascii for carriage return LF equ 10 ;ascii for line feed DEL equ 7fh ;ascii DEL BS equ 08h ;ascii backspace CTRLC equ 03 ;ascii for control-c JMPINST equ 0c3h ;jump instruction opcode org 0100h ;----------------------------------------------------------------------------- ; Initialize for transfer ;----------------------------------------------------------------------------- init lxi sp,ourStk ;initialize stack pointer call chkCpm ;set flag for CP/M or not call sizeRam ;determine amount of RAM available mvi a,MINDRV ;a=default drive sta drvNum ;need for pgmExit ; Display welcome message, then get the drive number and port number ; to use for the transfer from the operator. lxi h,mWelcom ;display welcome message call dispMsg ; getDrv - get drive number or letter from the user. getDrv lxi h,mDrive ;display drive number prompt call dispMsg call rcvCon ;get byte from the console ori 20h ;upper case letters to lower case cpi 'x' ;exit requested? jz pgmExit ;yes lxi h,baseDrv ;ascii 1st drive - baseDrv = 0 sub m jm getDrv ;error, entry was < first drive cpi MAXDRV-MINDRV+1 ;validate <= max drive jnc getDrv ;invalid, prompt again adi MINDRV ;restore offset of 1st drive (if any) sta drvNum ;save the drive number to use ; Verify drive is not write protected call dRestor ;restore to track 0 (selects drive) call dChkWP ;check write protect (aborts to getDrv) ; getPort - get pseudo port number 1-3 from the user. getPort lxi h,mPort ;display transfer port prompt call dispMsg call rcvCon ;get byte from the console ori 20h ;upper to lower case, nums not affected cpi 'x' ;exit requested? jz pgmExit ;yes sui '1' ;validate >= '1' jm getPort ;invalid, prompt again cpi 3 ;validate <= '3' jnc getPort ;invalid, prompt again inr a ;binary 1-3 sta xfrPort ;save the pseudo port to use ; Prompt the user to insert the disk to write, initialize, then start ; sending NAK to start the XMODEM reception. lxi h,mStart ;display start file transfer prompt call dispMsg mvi a,0ffh ;init disk driver track number sta curTrk ;to "invalid" xra a ;init track we're reading to zero sta trkNum inr a ;init xmodem block number to one sta blkNum lxi h,trkBuf ;trkBuf is initial starting point for shld rcvPtr ; XMODEM receive mvi a,NAK ;send starting nak after purging input data call purgSio ;----------------------------------------------------------------------------- ; mainLp - Receive as many packets as will fit into RAM and then write ; tracks to disk. Repeat until entire disk is written. ;----------------------------------------------------------------------------- mainLp call bufPkts ;buffer as many packets as possible xchg ;de=start of last packet received lxi h,trkBuf ;hl=start of track buffer shld trkPtr ;writing will start from here call cmpDeHl ;compare last packet start - trkBuf jc allDone ;nothing received but an EOT ; trkLoop - write tracks until all disk tracks have been written or all ; the tracks in the buffer have been written. trkLoop call wrtTrk ;write a track (hl returns->next track) lda trkNum ;increment track number (preserve HL) inr a sta trkNum cpi NUMTRK ;done all tracks? jz allDone ;yes xchg ;de=next track pointer lhld rcvPtr ;hl=start of last packet received call cmpHlDe ;compare rcvPtr-start of next track jnc trkLoop ;another track is present ; All the buffered tracks have been written. Move any bytes left over ; from the xmodem receive to the start of trkbuf. Adjust rcvPtr ; to the end of that data which is where reception of the next ; packet will begin. call subHlDe ;hl=hl-de = left over byte count - PKTLEN mov a,l ;a=left over count - PKTLEN adi PKTLEN ;a=left over byte count mov b,a ;b=left over byte count (bytes to move) lxi h,trkBuf ;hl->start of trkBuf jz mvDone ;no bytes to move, we're done ; Copy the leftover data to the start of trkBuf. The next XMODEM receive ; sequence will start at the end of this copied data instead of at the ; start of trkBuf. moveLp ldax d ;move from end of trkBuf to start of trkBuf mov m,a inx h ;hl->starting bytes of trackbuf inx d ;de->unwritten bytes from end of trkBuf dcr b jnz moveLp mvDone shld rcvPtr ;save address where to receive next packet mvi a,ACK ;ACK the last packet or EOT call sndByte jmp mainLp ;go receive more packets ; allDone - The disk is completely written. ACK the final packet (or EOT), ; display the "transfer complete" message and start the program over. allDone mvi a,ACK ;ACK the final packet or EOT call sndByte lxi h,mDone ;hl->done message call dispMsg jmp getDrv ;start over asking for drive num ;----------------------------------------------------------------------------- ; bufPkts - Receive XMODEM packets until we fill all the track buffer ; space available. Reception starts at the address pointed to by ; rcvPtr. Reception is stopped when the last packet exactly ; fills or "crosses" the end of the last track spot in RAM. rcvPtr is ; updated on exit to point to the start of the last packet received. ; The disk write routines use this pointer to know where data ends and ; then update rcvPtr to start to where to begin receiving new packets. ;----------------------------------------------------------------------------- bufPkts call rcvPkt ;receive a packet xchg ;de=current xmodem receive pointer cpi EOT ;EOT received? jz bufDone ;yes, buffering is done lhld bufEnd ;hl=end of buffering space call cmpDeHl ;compare current-end jnc bufDone ;past or at end, buffering is done mvi a,ACK ;otherwise, ACK the packet call sndByte xchg ;put xmodem rcv pointer back in HL jmp bufPkts ;buffer some more ; bufDone - no more room for packets. Update rcvPtr to point to the ; start of the last packet received so the disk write routines know ; where data ends. bufDone xchg ;hl=current xmodem receive pointer lxi d,-PKTLEN dad d ;hl=hl-PKTLEN = start of last rcv'd packet shld rcvPtr ;save use by write tracks ret ;----------------------------------------------------------------------------- ; rcvPkt - receive an xmodem format 128 byte packet. HL points to the 128 byte ; buffer to receive. HL is updated by 128 bytes after each succuessful ; reception of a new 128 byte packet. ;----------------------------------------------------------------------------- nakPkt pop h ;get HL back mvi a,NAK call purgSio ;purge receive for 1s then transmit NAK ;wait for SOH or EOT rcvPkt push h ;save HL for retries waitSoh call chkQuit ;check for user abort mvi a,3 ;3 second timeout for soh call rcvByte ;receive character with timeout jz nakPkt ;timeout cpi SOH ;SOH received? jz haveSoh cpi EOT ;EOT to say we're done? jnz waitSoh ;no, keep looking for SOH pop h ;undo the entry push ret ;return with EOT ; Have SOH, receive block number and not block number haveSoh mvi a,1 ;one second timeout once inside a packet call rcvByte ;get the block number jz nakPkt ;timeout, NAK and start over sta rcvBlk ;save the received block number mvi a,1 ;one second timeout call rcvByte ;get not block number jz nakPkt ;timeout, NAK and start over sta rcvNBlk ;save not block number ; Receive the 128 byte block mvi e,0 ;init checksum mvi d,pktLen ;d is byte counter pktLoop mvi a,1 ;one second timeout call rcvByte ;get next data byte jz nakPkt ;timeout mov m,a ;store the character inx h ;point to next byte add e ;update the checksum mov e,a dcr d ;decrement bytes remaining jnz pktLoop ; Verify the checksum mvi a,1 ;one second timeout call rcvByte ;get the checksum jz nakPkt ;timeout cmp e ;checksum match? jnz nakPkt ;no, packet error ; Checksum good. Verify block numbers lda rcvNBlk ;verify rcvBlk = not (rcvNBlk) cma ;A should = block number now mov b,a ;save in b lda rcvBlk cmp b ;compare rcvBlk = not(rcvNBlk)? jnz nakPkt ;no, error lda blkNum ;compare rcvBlk = expected blkNum cmp b jnz nakPkt ;nak if not the same (also nak's re-send) inr a ;increment expected block number sta blkNum pop b ;get HL off stack, but don't clobber HL xra a ;return a zero ret ;----------------------------------------------------------------------------- ; wrtTrk - write and verify NUMSEC sectors to the current track in trkBuf ; as pointed to by trkPtr. After the track is written, trkPtr is updated ; by the length of a track to point to the next track buffer. This ; saved value is also returned in HL. ;----------------------------------------------------------------------------- wrtTrk lxi h,trkNum ;get desired track from trkNum mov l,m ;and put into l call dSeek ;seek to the track call dChkWP ;check write protect mvi a,WRTRIES ;init write retry counter sta wrRetry rtryWrt mvi d,NUMSEC ;d=count of sectors to write ; wrtLoop - write a full track starting with any sector wrtLoop call dNxtSec ;wait for next (any) sector sync lhld trkPtr ;hl->start of current track buffer dad b ;bc has offset of sector within track call dWrite ;write the sector dcr d ;decrement sector count jnz wrtLoop ;loop until all sectors written ; Verify the track just written call dNxtSec ;force a 1 sector delay for trim erase mvi d,NUMSEC ;d=count of sectors to write vfyLoop call chkQuit ;check for ctrl-c call dNxtSec ;wait for next (any) sector sync lhld trkPtr ;hl->start of current track buffer dad b ;bc has offset of sector within track call dVerify ;verify the sector jnz vfyRtry ;error, go to retry logic vfyNext dcr d ;decrement sector count jnz vfyLoop ;loop until all sectors verified ; Track verified, increment trkPtr to memory location for next track ; and exit. lhld trkPtr ;hl=current track pointer lxi d,TRKLEN ;de=bytes in a track dad d ;hl=start of next track in trkBuf shld trkPtr ret ; vfyRtry - verify failed, retry reads followed by a re-write ; of the track in needed. vfyRtry mvi a,RDTRIES ;init retry counter sta rdRetry retryLp call chkQuit ;check for ctrl-c call dWtSec ;wait for sector specified in e lhld trkPtr ;hl->start of current track buffer dad b ;bc has offset of sector within track call dVerify ;verify the sector jz vfyNext ;success, go verify next sector ; Re-verify failed. Decrement retry count and try again if not zero. Once ; retry counter expires, do another write. lxi h,rdRetry ;decrement the read retry counter dcr m jnz retryLp ;try again ; read re-tries expired, decrement the write retry count and re-write ; if not expired lxi h,wrRetry ;decrement the write retry counter dcr m jnz rtryWrt ;retry starting with the write ;fall through to disk fail ;----------------------------------------------------------------------------- ; Disk write failure. Display the track and sector with the error. Restart ; the program. ;----------------------------------------------------------------------------- lxi h,errTrk ;hl->where to put ascii decimal lda trkNum ;a=track with error on it call bin2dec ;track to ascii lxi h,errSec ;hl->where to put ascii sector mov a,e ;a=sector where error occured call bin2dec lxi h,mDskErr ;display the error message call dispMsg lxi sp,ourStk ;initialize stack pointer jmp getDrv ;start over asking for drive num ;----------------------------------------------------------------------------- ; dispMsg - display the null-terminated message passed in hl on the ; console device. Clobbers b, hl ;----------------------------------------------------------------------------- dispMsg mov a,m ;get the next message byte ora a ;null terminates rz mov b,a ;conOut wants character in b call conOut inx h ;move to next byte jmp dispMsg ;----------------------------------------------------------------------------- ; rcvCon - Receive a character from the console device, echo it, then ; wait for a CR. Exits program if Ctrl-c typed. Returns with invalid ; character (null) if BS or DEL pressed after 1st character ; Returns character in a, clobbers b. ;----------------------------------------------------------------------------- rcvCon call conIn ;check for input jz rcvCon ;nothing ani 7fh cpi CTRLC ;abort requested? jz pgmExit ;yes cpi CR ;return pressed? rz ;yes, don't echo it mov b,a ;conOut needs character in b call conOut ;echo it ; Wait for CR, then return 1st character typed rcvCr call conIn ;check for input jz rcvCr ;nothing ani 7fh cpi CTRLC ;abort requested? jz pgmExit ;yes cpi DEL ;delete rz ;yes, return DEL character cpi BS ;backspace? rz ;yes, return BS character cpi CR ;return pressed? jnz rcvCr ;no, keep waiting mov a,b ;return 1st character typed ret ;----------------------------------------------------------------------------- ; purgSio - Wait for 1 second of idle time on receive line, then transmit the ; character passed in A (usually ACK or NAK). Clobbers b ;----------------------------------------------------------------------------- purgSio push a purge mvi a,1 ;1 second timeout call rcvByte ;wait for 1 second without activity jnz purge pop a ;get back the character to send ;fall through to sndByte ;----------------------------------------------------------------------------- ; sndByte - send the byte in a through the specified transfer port. ; Clobbers b. (assuming serOut clobbers only a). ;----------------------------------------------------------------------------- sndByte mov b,a ;b=byte to transmit lda xfrPort ;a=port to use for transfer jmp serOut ;send the character ;----------------------------------------------------------------------------- ; rcvByte - Receive a byte from the specified transfer port. A wait timeout ; in seconds is specified in a. If a timeout occurs, zero is returned in ; a and the zero flag is true. Otherwise, the character is returned in a ; (could be zero) and the zero flag is false. ONESEC must be set based on ; processor speed and the number of cycles in the serIn call + 59. serIn ; on the Sol-20 is the AINP call and takes about 136 cycles. ; Clobbers a, b and c. ;----------------------------------------------------------------------------- rcvByte push d ;save d, e mov d,a ;save timeout in d initSec lxi b,ONESEC ;bc=cycles through this loop for 1s rcvWait lda xfrPort ;(13) a=port to use for transfer call serIn ;(17+136) look for a byte jnz haveChr ;(10) byte received dcx b ;(5) otherwise, decrement timer mov a,b ;(5) one second expire? ora c ;(4) jnz rcvWait ;(10) no, keep waiting ; one second timeout has occured. Decrement the seconds counter. dcr d ;decrement seconds counter jnz initSec ;initialize for another 1 second count haveChr pop d ;restore d, e ret ;-------------------------------------------------------------- ; bin2dec - Binary byte in A to 2 ASCII digits at (HL) ; HL is preserved ;-------------------------------------------------------------- bin2dec mvi m,' ' ;assume zero supression sui 10 ;value less than 10? jc do1s ;yes, leading blank mvi m,'1' ;have one ten already loop10 sui 10 ;count 10s jc do1s ;done with 10s, do 1s inr m jmp loop10 do1s adi '0'+10 ;form ASCII 1s digit inx h ;move to 1s position mov m,a dcx h ;restore hl ret ;-------------------------------------------------------------- ; chkQuit - check for the user to request abort (ctrl-c). If ; a character is present on the console port, read it and ; see if ctrl-c. Clobbers A ;-------------------------------------------------------------- chkQuit call conIn ;check for console input rz ani 7fh cpi CTRLC ;abort requested? rnz ;no ; Ctrl-C typed while program is running. Return to drive prompt. lxi sp,ourStk ;initialize stack pointer jmp getDrv ;start over asking for drive num ;-------------------------------------------------------------- ; pgmExit - Exit to CP/M or to the monitor ROM based on the ; CP/M flag ;-------------------------------------------------------------- pgmExit lda cpmFlag ;running under CP/M? ora a jnz cpmExit ;yes ; Exit to ROM monitor lxi h,mExit ;display "exiting" message call dispMsg jmp MONITOR ;jump to ROM monitor entry point ; CP/M exit. If boot drive was used, prompt user to insert CP/M disk cpmExit lda drvNum ;boot drive used? sui MINDRV jnz noDisk ;not 1, disk prompt not needed lxi h,mCpm ;display "insert cp/m disk" call dispMsg call rcvCon ;wait for a character noDisk lxi h,mExit ;display "exiting" message call dispMsg jmp CPMBOOT ;reboot CP/M ;-------------------------------------------------------------- ; chkCpm - check if running under CP/M. CP/M flag is set true ; (non-zero) if yes, cleared otherwise. ;-------------------------------------------------------------- ; First, initialize entries for stand-alone chkCpm xra a sta cpmFlag ;clear CP/M flag mvi a,MINDRV+'0' ;ascii for minimum drive number sta mDrvMin ;store in the drive prompt message sta baseDrv ;ascii 1st drive - baseDrv = 0 mvi a,MAXDRV+'0' ;ascii for max drive number sta mDrvMax ; Determine if we're under CP/M or standalone. CP/M is assumed if ; a jump instruction is present at the CP/M warm start location (0) ; and five more jumps (e.g., a jump table) is present at the ; jump-to destination. lda WBOOT ;see if jump instruction present for CP/M cpi JMPINST rnz ;no, not CP/M ; A jump instruction is present at the CP/M warm boot location (0), ; now see if that jump points to five more jumps. If so, assume CP/M lxi h,WBOOT+1 ;point to lsb of jump address mov e,m ;e=low byte of jump inx h mov d,m ;de=destination of jump mvi b,5 ;look for 5 more jumps (a jump table) jmpTest ldax d ;a=opcode at jump destination sui JMPINST ;another jump present? rnz ;no, not CP/M inx d ;move to next jump inx d inx d dcr b jnz jmpTest dcr a ;a=0ffh sta cpmFlag ;CP/M flag to non-zero = true ; We're running under CP/M. Change drive prompt message to show drive ; letters instead of drive numbers and change baseDrv to convert ; an 'A' to the base drive number (MINDRV). mvi a,'A' ;'A' in drive message instead of number sta mDrvMin adi MAXDRV-MINDRV ;max drive letter sta mDrvMax mvi a,'a' ;ascii 1st drive - baseDrv = 0 sta baseDrv ret ;------------------------------------------------------------------ ; sizeRam - determine how much RAM we have for buffering tracks. ; Sets the bufEnd variable which points to end address of ; the last possible track buffer + 1 ;------------------------------------------------------------------ sizeRam lxi h,(trkBuf+0ffh) AND 0ff00h ;next 256 byte boundary ramLoop mov a,m ;a=current RAM content inr m ;change RAM cmp m ;did RAM change? mov m,a ;restore RAM jz ramEnd ;end of RAM found inr h ;next page jnz ramLoop ; ramEnd - end of RAM found. Subtrack 256 bytes from this to make room ; for xmodem packet overflow at the end. Determine the end address + 1 ; of the last track buffer that will fit in RAM. Store in bufEnd ramEnd xchg ;de=end of RAM + 1 mvi a,-((TRKLEN SHR 8) + 1) AND 0ffh cmp d ;force de < (10000h - TRKLEN) jnc topOk mov d,a ;limit max address topOk dcr d ;subtract 256 bytes from end of RAM lxi h,trkBuf ;hl=start of track buffer lxi b,TRKLEN ;bc=length of track in bytes ; Loop increasing hl by TRKLEN until hl > end of RAM. bfEndLp dad b ;hl=hl+track length call cmpHlDe ;compare hl-de jc bfEndLp ;still more room, keep going ; Subtrack one track length from hl, this will be the end address + 1 of ; the the last track buffer that will fit in RAM lxi b,-TRKLEN ;subtract one track length dad b ;hl = end address of last track + 1 shld bufEnd ;save as bufEnd ret ;-------------------------------------------------------------------- ; cmHlDe - compare HL-DE. Carry set if HL=DE ;-------------------------------------------------------------------- cmpHlDe mov a,l ;compare HL-DE, do lsbs first sub e mov a,h ;do msbs sbb d ret ;carry set HL=DE ;-------------------------------------------------------------------- ; cmDeHl - compare DE-HL. Carry set if DE=HL ;-------------------------------------------------------------------- cmpDeHl mov a,e ;compare DE-HL, do lsbs first sub l mov a,d ;do msbs sbb h ret ;carry set DE=HL ;-------------------------------------------------------------------- ; subHlDe - HL=HL-DE ;-------------------------------------------------------------------- subHlDe mov a,l ;subtract HL-DE, do lsbs first sub e mov l,a ;lsb result in l mov a,h ;do msbs sbb d mov h,a ;msb result in h ret ;----------------------------------------------------------------------------- ; Data Area ;----------------------------------------------------------------------------- mWelcom db cr,lf,lf db '===== PC to Floppy Image Transfer, Version 1.3 =====' db cr,lf,lf db 'This program writes a Northstar single density floppy from a' db cr,lf db 'disk image received through a Sol-20 port via XMODEM.',0 mDrive db cr,lf,lf,'Insert and specify destination drive (' mDrvMin db 'x-' mDrvMax db 'x) or X to exit: ',0 mPort db cr,lf,lf db 'Specify the pseudo port to use for file transfer',cr,lf db ' 1) Default serial port',cr,lf db ' 2) Default parallel port',cr,lf db ' 3) User-defined pseudo port',cr,lf db 'Enter port or X to exit: ',0 mStart db cr,lf,lf db 'Start XMODEM send now...',0 mDone db cr,lf,lf,'Creation of new disk successful!',cr,lf,0 mNoTrk0 db cr,lf,lf,'Seek to track 0 failed',cr,lf,0 mStkTk0 db cr,lf,lf,'Drive stuck on track 0',cr,lf,0 mDskErr db cr,lf,lf,'Write failure on track ' errTrk db 'xx, sector' errSec db 'xx. Disk creation aborted.',cr,lf,0 mWrtPrt db cr,lf,lf,'Disk is write protected',cr,lf,0 mExit db cr,lf,lf,'Exiting...',cr,lf,0 mCpm db cr,lf,lf db 'Insert CP/M disk into drive A, then press Return...',0 ;**************************************************************************** ; ; Hardware specific console and serial I/O routines. ; The following four routines must be written to provide a common ; interface to the hardware on which this program is running. The ; port number specified for serIn and serOut matches the port number ; input from the operator via the port menu. ; ;**************************************************************************** ; The rcvByte subroutine above times a one second timeout with a code ; loop that calls the hardware specific serIn routine below. ONESEC ; must be set based on processor speed and the number of cycles in ; the serIn call + 59. serIn on the Sol-20 is the AINP call and takes ; about 136 cycles. ONESEC equ 10256 ;rcvByte loop count for 1 second ;---------------------------------------------------------------------------- ; conIn - input character from console ; inputs: ; outputs: z true if no character present ; z false if character returned in a ; clobbers none ;---------------------------------------------------------------------------- conIn equ CUTER+01Fh ;SOLOS/CUTER conIn routine ;---------------------------------------------------------------------------- ; conOut - output character to console ; inputs: b = character to send ; clobbers none ;---------------------------------------------------------------------------- conOut equ CUTER+019h ;SOLOS/CUTER conOut routine ;---------------------------------------------------------------------------- ; serIn - input character from port specified in a ; inputs: a = port to read from ; outputs: z true if no character present ; z false if character returned in a ; clobbers none ;----------------------------------------------------------------------------- serIn equ CUTER+022h ;SOLOS/CUTER READ from pseudo port in A ;----------------------------------------------------------------------------- ; Hardware specific I/O ; serOut - output character to port specified in a ; inputs: a = port to transmit through ; b = character to send ; clobbers none ;----------------------------------------------------------------------------- serOut equ CUTER+01Ch ;SOLOS/CUTER WRITE B to pseudo port in A ;**************************************************************************** ; ; Northstar disk I/O routines, mostly written by M Eberhard. ; ; Commands are issued to the N* controller by *reading* memory ; locations. Data and Control bits to the controller are embedded ; in the read addresses. What you get back depends on what you ; read - either one of 2 status bytes, or disk data. The commands ; constructed below take care of this. ; ; 2 RAM locations are needed: ; curTrk is used to remember the current track ; and should be initialized to 0FFh ; drvNum remembers the current unit. On the N* controller, ; disks are 1 to 3. 0 means none selected. ;************************************************ ; Northstar disk Controller Addresses & Commands ; (Read or write from address to execute command) ;************************************************ NSCTRL equ 0E800h ;Northstar disk controller NSBOOT equ NSCTRL+100h ;disk boot PROM entry point NWRITE equ NSCTRL+200h ;write prefix (data in low byte) NCOMND equ NSCTRL+300h ;commands base address NASTAT equ NCOMND+10h ;read A status NBSTAT equ NCOMND+30h ;read B status NSTRST equ NCOMND+09h ;set track step flip flop NCTRST equ NCOMND+08h ;clear track step flip flop NSTOUT equ NCOMND+1Ch ;set step direction to 'out' NSTIN equ NCOMND+1Dh ;set step direction to 'in' NMOTRA equ NCOMND+90h ;motor on, read A status NRBYTE equ NCOMND+50h ;read a byte of disk data NWRCRD equ NCOMND+04h ;start write-sector sequence NRSEFL equ NCOMND+94h ;reset sector flag, restart motor timeout ;**************************** ; Disk Controller Status Bits ;**************************** NSTR0 equ 1 ;NASTAT: track 0 NSWP equ 2 ;NASTAT: write protect NSBDY equ 4 ;NASTAT: body (sync chr found) NSWRT equ 8 ;NASTAT: write ready NSMO equ 10h ;NASTAT & NBSTAT: motor on NSWN equ 40h ;NASTAT & NBSTAT: window flag NSSF equ 80h ;NASTAT & NBSTAT: sector flag NSSP equ 0Fh ;NBSTAT: sector position ;************************** ; Disk Controller constants ;************************** SYNC equ 0FBh ;header sync charactor WRMUP equ 32h ;warm up time in sectors SETL equ 0Dh ;head settling time in sectors SWAIT equ 08Ch ;max wait for body before error MAXUNT equ 3 ;max disk drive unit (0 not allowed) MAXTRK equ 39 ;max track MAXSEC equ 9 ;max sector (SA400 and SA400L) ;************************** ;Disk Subsystem Subroutines ;************************** if 0 ;read not used ;***Subroutine************************************** ; dRead - Read sector on current track. ; On Entry: ; Drive is selected, motor is running and sector ; sync was just previously detected ; hl->sector buffer ; On Exit: ; clobbers b,c,h,l ;*************************************************** ;Wait for sector body dRead: mvi b,SWAIT ;max time until body NSRD1: dcr b jz NOSYNC ;NO sync character lda NASTAT ;LOOK for sync chr ani NSBDY jz NSRD1 ;keep looking lxi b,0 ;initial CRC ;read sector data NSRD2: lda NRBYTE ;read a byte mov m,a ;save it xra b ;compute checksum rlc mov b,a inx h ;bump pointers dcr c ;256 byteS jnz NSRD2 ;check CRC lda NRBYTE ;get disk CRC xra b ;compare to computed CRC ret ;Z flag set if OK, else error endif NOSYNC: dcr b ;b=0ffh ret ;return with zero false ;***Subroutine************************************** ; dVerify - Verify sector on current track ; On Entry: ; Drive is selected, motor is running and sector ; sync was just previously detected ; hl->buffer to compare against ; On Exit: ; clobbers b,c,h,l ;*************************************************** ;Wait for sector body dVerify: mvi b,SWAIT ;max time until body NSVF1: dcr b jz NOSYNC ;NO sync character lda NASTAT ;LOOK for sync chr ani NSBDY jz NSVF1 ;keep looking lxi b,0 ;initial CRC ;read and verify sector data NSVF2: lda NRBYTE ;read a byte cmp m ;compare to validate data rnz ;match fail xra b ;compute checksum rlc mov b,a inx h ;bump pointer dcr c ;256 byteS jnz NSVF2 ;check CRC lda NRBYTE ;get disk CRC xra b ;compare to computed CRC ret ;Z flag set if OK, else error ;***Subroutine************************************** ; dWrite - Write a sector on current track ; On Entry: ; Drive is selected, motor is running and sector ; sync was just previously detected ; hl->buffer address ; On Exit: ; clobbers b,c,h,l ;*************************************************** dWrite: push d ;save de lda NWRCRD ;prepare to write NSWR1: lda NASTAT ;wait for controller ani NSWRT ;..to get ready to write. jz NSWR1 ;write sector header lxi b,NWRITE ;C=0,b=write prefix lxi d,000FH ;number of header zeros, ;init CRC calculation NSWR2: ldax b ;write now. dcr e ;count header zeros jnz NSWR2 ;write sector sync character mvi c,SYNC ;write sync chr ldax b ;write sector data NSWR3: mov c,m ;get chr from RAM mov a,c ;Calculate CRC xra d ;keep it in d rlc mov d,a ldax b ;write chr to disk inx h ;bump pointers dcr e ;write 256 bytes jnz NSWR3 ;write sector CRC mov c,d ;Write CRC ldax b pop d ;restore de ret ;don't wait for end of sector ;***Subroutine************************************** ; dChkWP - check if disk is write protected. ; On Entry: ; disk already selected ; On Exit: ; returns if disk is not protected. Otherwise, an error ; message is displayed and the program started over. ; clobbers h,l ;*************************************************** dChkWP lda NASTAT ;check for write protect ani NSWP rz ;return if not protected ; Disk write protected. Display error and restart program. lxi h,mWrtPrt ;otherwise, display error message call dispMsg ;display the error lxi sp,ourStk ;re-init stack point jmp getDrv ;***Subroutine************************************** ; dSeek - Seek Track. The motor timeout is restarted ; as a result of this call. ; On Entry: ; curTrk = current track, FFh means we don't know ; l = desired track ; On Exit: ; curTrk = current track ; trashes all registers ;*************************************************** dSeek: call NSELCT ;select drive, spin up lda curTrk ;where are we? cpi 0FFh cz dRestor ;lost: restore ;compute the required number of steps and the direction sub l ;which way to step? rz ;already there? lxi d,NSTOUT ;desiredcurrent NSSK1: ;remember target track, and step there mov h,a ;number of steps mov a,l ;remember target track sta curTrk ;..is new track, when done jmp NSSTEP ;go step h tracks ;***Subroutine************************* ; dRestor - Restore to track 0 ; On Exit: ; Z set and curTrk=0 if track 0 found ; trashes psw,bc,h ;************************************** dRestor: call NSELCT ;select drive, spin up ;step in once, in case the head is lost lxi d,NSTIN ;step in once mvi h,1 call NSSTEP ;step in now jz STUCK0 ;z: Stuck on track 0 ;step out enough times that we should find track 0 even ;if the SA400's actuator is out of its spiral groove mvi h,MAXTRK+16 ;more than enough lxi d,NSTOUT ;prepare to step out call NSSTEP ;steppin' out to track 0 rz ;z set: found track 0 ;Error: track 0 not detected despite many steps outward lxi h,mNoTrk0 call dispMsg lxi sp,ourStk ;initialize stack pointer jmp getDrv ;start over asking for drive num ;Error: stuck on track zero STUCK0: lxi h,mStkTk0 ;stuck on track zero message call dispMsg lxi sp,ourStk ;initialize stack pointer jmp getDrv ;start over asking for drive num ;***Subroutine************************* ; Step head ; On Entry: ; h = number of steps, >0 ; de = step command - NSTIN or NSTOUT ; On Exit: ; Z set and curTrk=0 if track 0 found ; trashes psw,bc,h ;************************************** NSSTEP: ldax d ;set step direction NSSTP1: lda NSTRST ;Reset step flipflop xthl ;must be here for delay. xthl ;at least 10 uSec mvi b,2 ;step timer lda NCTRST ;set step flipflop call NSWAIT ;wait 2 sector times lda NASTAT ;at track 0? ani NSTR0 jnz NSSTP2 ;y: stop stepping dcr h ;n: step again jnz NSSTP1 ;stepped enough? inr a ;clear Z: not track 0 ret ;on track 0. clear curTrk, and return with Z set NSSTP2: xra a ;set Z: track 0 sta curTrk ret ;***Subroutine******************************** ; NSELCT - Select drive. Turn on the motor if necessary, ; wait for head settle time. ; On Entry: ; drvNum = desired drive (1-3) ; trashes psw,bc ;********************************************* NSELCT: mvi b,WRMUP-SETL ;warmup motor time ;long delay if motors are off lda NMOTRA ;turn on drive motors ani NSMO ;already on? cz NSWAIT ;n: spin up ;select drive, load head mvi b,NCOMND/256 ;select drive lda drvNum mov c,a ldax b ;Load head on drive c ;wait for settling time after head load mvi b,SETL ;head settling time ;fall into NSWAIT to stall ;***Subroutine*********************** ; Wait for b sectors ; On Entry: ; b = number of sectors to wait for ; trashes psw,b ;************************************ NSWAIT: lda NRSEFL ;reset sector flag, restart motor timeout NSWAT1: lda NASTAT ;get status a rlc ;test NSSF, sector flag set? jnc NSWAT1 ;no dcr b ;wait for b sectors to pass rz jmp NSWAIT ;***Subroutine****************************** ; dWtSec - Wait for specified sector. ; On Entry: ; Drive is selected, motor is running ; e = desired sector number ; On Exit: ; drive is at proper sector ; e = sector number ; bc = byte offset of this sector in a track ;******************************************* dWtSec: lda NRSEFL ;reset sector flag, restart motor timeout NSWTS1: lda NBSTAT ;a=sector flag and sector number rlc ;test NSSF, sector flag set? jnc NSWTS1 ; Sector flag set, get sector number alone and see if this ; is the right sector lda NBSTAT ;get sector again to be safe ani NSSP ;get sector bits alone cmp e ;the sector we want? jnz dWtSec ;no, keep looking ; Compute the offset of this sector within the track and return in bc mov b,a ;256 bytes per sector mvi c,0 ret ;***Subroutine****************************** ; dNxtSec - Wait for next (any) sector ; On Entry: ; Drive is selected, motor is running ; On Exit: ; drive is at proper sector ; e = sector number ; bc = byte offset of this sector in a track ;******************************************* dNxtSec: lda NRSEFL ;reset sector flag, restart motor timeout NSWTS2: lda NBSTAT ;a=sector flag and sector number rlc ;test NSSF, sector flag set? jnc NSWTS2 ; Sector flag set, return the sector number in e and compute the offset ; of this sector within the track. lda NBSTAT ;get sector again to be safe ani NSSP ;get sector bits alone mov e,a ;return sector in e mov b,a ;256 bytes per sector mvi c,0 ret ;************************************************************************** ; ; Data area ; ;************************************************************************** ; disk variables drvNum ds 1 ;drive number to use curTrk ds 1 ;current track number drive is on trkNum ds 1 ;track number to read trkPtr ds 2 ;pointer into trkBuf (start of next track) bufEnd ds 2 ;end address + 1 of last track spot in RAM ; retry logic variables rdRetry ds 1 ;disk read retry counter wrRetry ds 1 ;disk write retry counter ; xmodem variables xfrPort ds 1 ;pseudo port for file transfer blkNum ds 1 ;current xmodem block number rcvBlk ds 1 ;block number received rcvNBlk ds 1 ;"not" block number received rcvPtr ds 2 ;pointer for start of XMODEM send ; misc variables cpmFlag ds 1 ;non-zero if running under CP/M baseDrv ds 1 ;ascii 1st drive - baseDrv = 0 ds 64 ;stack space ourStk equ $ ; track buffer runs from here to the end of memory trkBuf equ $ end