;**************************************************************************** ; FILE UPLOAD UTILITY FOR CIS A PROTOCOL. ; WRITTEN 3/17/82 BY BOB RICHARDSON ; COPYRIGHT (C) 1982 PERFORMANCE BUSINESS MACHINES ; program distributed by permission- further distribution must contain this ; notice, the copyright notice and the authors name ; ; INVOKED BY "UPLOAD FNAME.FTP" AND USES DEFAULT FCB AND COMMAND LINE ; ************************************************************************* .z80 ; equates soh equ 01h ; start of header etx equ 03h ; end of text eot equ 04h ; end of transmission enq equ 05h ; enq char - not used si equ 0fh ; shift in - starts protocol on terminal so equ 0eh ; shift out - ends protocol ; knak equ 15h ; nak dle equ 10h ; data link escape - used to mask chars for transparency esc equ 1bh ; escape eof equ 1ah ; ctl-z ctlz equ 1ah ; also cr equ 0dh ; carriage return lf equ 0ah ; line feed tof equ 0ch ; top of form ; cldboot equ 00h ; bios coldboot vector iobyte equ 0003h ; addr of iobyte deffcb equ 05ch ; addr of default fcb command equ 080h ; addr of command line bdos equ 05h ; addr of bdos jmp ; BDOS FUNCTIONS prnstg equ 09h ; print string delimited by $ rdcbuf equ 0ah ; read console buffer function fn$opn equ 0fh ; open disk file fn$cls equ 010h ; close disk file fn$del equ 013h ; delete disk file fn$rds equ 014h ; read sequential fn$wts equ 015h ; write sequential fn$mak equ 016h ; make file fn$ren equ 017h ; rename file fn$std equ 01ah ; set dma function ; ; BIOS OFFSETS FOR VARIOUS CALLS const equ 03h ; constat call conin equ 06h ; conin conout equ 09h ; character out to console list equ 0ch ; character to line printer punch equ 0fh ; char to punch device rdr equ 12h ; get char from reader device reader equ 12h ; alternate spelling ; FCB OFFSETS current equ 32 ; offset to current record number ftype equ 09 ; and offset to type ; Version info vers equ '1' ; ascii version rev equ '2' ; and rev level ; History info ; 3/20/1982 FIRST COMPLETE VERSION RELEASED ; BY THE AUTHOR BOB RICHARDSON OF MICROPRO INTL ; CORPORATION - FURTHER DISTRIBUTION MUST CONTAIN ; THIS COMMENT - this file made available courtesy ; of MicroPro International Corp. and the author ; ;************************************************************************** ;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ; code begins: ; MAIN DRIVER LOOP FOR THE UPLOAD PROTOCOL ; upld: ld sp,upld ; the Charlie Strom memorial local stack call announce ; copyrite and vers, rev level call dskinit ; initialize disk buffer call procol ; turn on protocol, open file, and start upldrt: call sndhdr ; then send the header for file xfer call waitack ; and wait for ack response jp c,upldrt ; retry if nak response jp nz,comfail ; error so dump job call sendack ; else prompt for first record uplp: call getrec ; get terminals record jp c,uplp ; wait for resend if nak ld a,(seeneot) ; get eotflag cp 00h ; and test for completion jp nz,fin ; eof - recd eot record upl1: call putrec ; write rec(s) to disk jp c,dspacen ; no space on host disk - send fail message call z,sendack jp uplp ; loop till eof ; fin: call sendack ; ack eot message call complete ; turn off protocol and send all done message call fclose ; dump buffer tailings if any jp cldboot ; terminate ;************************************************************************ ; end of driver beginning of subroutines biosvct: ld hl,(cldboot+1) ;get start of bios table add hl,de ; get addr for branch jp (hl) ; return handled to inline location ;************************************************************************ ; Get rev and version and copyright notice to operator announce: ld de,cpyrite ; copyright notice call prnmes ; to console ret ; to caller ; cpyrite: defb cr,lf,'Upload Vers. ',vers,'.',rev,cr,lf defb ' Copyright (C) 1982 PBM Division MicroPro International Corporation ','$' ; ; ************************************************************************** ; Kudos to Russ Renshaw for inventing this protocol ; and special thanks to charlie, tom, and dave - sysops of the CIS CP-MIG ; without whose help none of this code would be here ; *************************************************************************** ; INITIALIZE THE PROTOCOL AND OPEN FILES procol: ld de,deffcb ; get default fcb ld c,fn$opn ; open file function call bdos ; see if we can open file cp 04h ; test for successful open jp c,isfil ; send file exists message if file there ld a,(command) ; get count of oper supplied chars or a ; and insure non zero value jp z,nospec ; complain if not right ld hl,deffcb+ftype ; addr of file type push hl ; and save for next use ld de,typsav ; save area ld bc,03h ; length of file type ldir ; move to save area - operator supplied file type pop de ; here is the next use of filetype addr in fcb ld hl,dollar ; $$$ for temporary file type ld bc,03h ; length of file type ldir ; move it in ; the above added for pip compatibility ld a,0 ; get zero ld (masking),a ; and start masking ctl chars in msg text call rmtnm ; prompt operator for name at his end ld a,(conbuff+1) ; start of data - contains byte count ld c,a ; is count for move ld b,0 ; with high order=0 ld hl,conbuff+2 ; start of actual name call noblnk ; bypass all blanks jp z,comfail ; if this passes machine is broken - get a new ; one. ld de,filespec ; addr in esc a message push bc ; save the number of non blanks ldir ; move filespec to message pop hl ; restore count ld a,cr ; get cr to terminate the esc a string ld (de),a ; and move it to the esc a message buffer end inc hl ; update count to reflect this fact ld (tmpsav),hl ; and save for next routine ; here we create the temporary $$$ file on the disk ld de,deffcb ; so make the file - all is well ld c,fn$del ; first delete it just in case push de ; save for next call call bdos ; to pyramid building routine pop de ; restore fcb pointer ld c,fn$mak ; make function call bdos ; mush cp 04h ; test sucessful completion jp nc,nodirsp ; else give error for no directory space ld a,0 ; get zero ld (deffcb+current),a ; to current record ret ; to caller ; tmpsav: defw 00h ; save area for operator count from ; remote file name ; ********************************************************************** ; send the esc a header to the terminal - refer to the protocol document ; for the format of this record - is essentially the same as normal ; but fields have special meanings. sndhdr: ; and then turn on protocol in terminal ld a,si ; get shift in char call punout ; send it ld a,esc ; send esc call punout ; charge ld a,'A' ; esc a for message call punout ; mush ye huskies mush ld hl,(tmpsav) ; get count from operator answer for name push hl ; move to bc ld hl,escames ; get message balance addr pop bc ; restore count from command line ld a,c ; get count in accumulator add a,escalen ; and add in normal length ld b,a ; get in byte counter call prmesout ; send message as normal xor a ; set z flag ret ; and return ; bypass leading blanks in command line noblnk: ld a,(hl) ; get char cp 20h ; test blank ret nz ; non blank dec c ; reduce count ret z ; return error if exhausted inc hl ; increment buffer pointer jp noblnk ; file exists on host- blow off terminal as security measure ; isfil: ld de,isflmes ; file found message call prnmes ; to console jp cldboot ; and terminate abnormally ; isflmes: defb cr,lf,'FILE ALREADY EXISTS ON HOST- CHECK DIRECTORY$' ; nospec is issued when user omits the ; filespec in the command line nospec: ld de,nospecm ; file found message call prnmes ; to console jp cldboot ; and terminate abnormally ; nospecm: defb cr,lf,'I am sorry- you must specify a name for upload$' ; error - the host has no directory space nodirsp: ld de,nodirmes ; no directory space call prnmes ; to console jp cldboot ; and terminate ; nodirmes: defb cr,lf,'NO DIRECTORY SPACE ON HOST !!!','$' ; ; message for ESC A header - sent if all is well to start upload escames: defb 'U' ; upload defb 'B' ; Binary transfer escalen equ $-escames ; length for send routine filespec: defs 16h ; name of file to upload typsav: defs 03h ; save area for file type until sucessfull dollar: defb '$$$' ; temporary file type in case of io error ; ;************************************************************************** ;get name for remote computer ; a response will cause the same name to be used as on host rmtnm: ld de,remquery ; ask the terminal what it wants to call it call prnmes ; to the operating system such as it is ld de,conbuff ; get a response call rdcon ; and then ld hl,conbuff+2 ; convert to insure upper case ld a,(conbuff+1) ; get char count xferred cp 0 ; insure some characters jp z,naminv ; else take default value ld c,a ; get counter for blank test to call noblnk ; further insure no error jp z,naminv ; else use same name as on host ld b,a ; in byte counter ; roll lower to upper case if necessary rmtnm1: ld a,(hl) ; pick up char cp 061h ; test for lower case jr c,rmtntl ; not lower if carry cp 07bh ; still looking if less than z jr nc,rmtntl ; so go on about business and 05fh ; else roll ld (hl),a ; and save rmtntl: inc hl ; bump character pointer djnz rmtnm1 ; and get next character ret ; and return to caller ; use same name as host for remote file ; naminv: ld hl,command+1 ; use the command line input ld de,conbuff+2 ; for the remote name ld a,(command) ; length ld c,a ; to counter with ld (conbuff+1),a ; count in command line ld b,0 ; zero high order ldir ; move characters ret ; to caller ; ; buffer for response to filename question conbuff: defb 010h ; sixteen bytes max I'll allow defb 00h ; initial count defs 16 ; and blank buffer ; remquery: defb cr,lf,' I need the file name on your computer',cr,lf,'->','$' ; ; ;*************************************************************************** ; TRANSMIT ACK OR NAK TO TERMINAL sendack: ld a,'.' ; get ack character jp acknak ; branch to common code ; sendnak: ld a,'/' ; nak char acknak: call punout ; send it scf ; insure carry reset for logic flow in mn loop ccf ; could have used or a , i know - good document ret ; but thats a subject for another time ;***************************************************************************** ; send a record using the CIS-A protocol ; used primarily for the esc a header in this program ; prmesout: push bc ; save byte count push hl ; save buffer pointer xor a ; get zero ld (chksum),a ; and init checksum ld a,soh ; get start of header char call punout ; and send it ld a,(currec) ; get current record call sumupd ; and update checksum call punout ; and send it pop hl ; restore buffer addr pop bc ; restore count to b ; pmeslp: push hl ; save pointer push bc ; and char count ld a,(hl) ; get char call sumupd ; update checksum call tstmsk ; test if masking necessary call punout ; send char pop bc ; restore count pop hl ; get buffer pointer inc hl ; increment it djnz pmeslp ; and loop until all done ; ld a,etx ; get etx char call punout ; send it ld a,(chksum) ; get check sum cp 020h ; test for < ascii space jp nc,pmesl1 ; if = or greater, do not mask or 040h ; else add to supply transparency push af ; save checksum ld a,dle ; send dle call punout ; to remote pop af ; restore char pmesl1: call punout ; send it ret ; and return ;************************************************************************* ; Test here for masking of control chars, handle if necessary ; control chars are masked to prevent confusion between innocent bit combos ; and protocol control chars tstmsk: push af ; save char ld a,(masking) ; get switch value cp 00h ; test for on status jp nz,tstmsr ; if off return immediate pop af ; restore original char push af cp 05h ; test if one of the offending chars jp c,tstms1 ; mask if so cp dle ; or if equal the dle jp z,tstms1 ; go masked cp knak ; or if = to jp z,tstms1 ; the fatal nak mask it ; common return tstmsr: pop af ret ; common return if no masking necessary ; masking needed - so mask it tstms1: ld a,dle ; send dle char first call punout ; and send it pop af ; followed by char+40 or 040h ; to insure transparecy ret ; masking: defb 00h ; flag for control char masking ; ;**************************************************************************** ; update the checksum ; called whenever we need checksumming - uses simple checksum algorithm sumupd: push af ; save char ld e,a ; and leave it in reg ld a,(chksum) ; get old checksum rlca ; and rotate it add a,e ; add new byte adc a,0 ; and possible carry ld (chksum),a ; and save it pop af ; restore character ret ; and return ;**************************************************************************** ; Read a record from the serial port ; using the Compuserve A protocol getrec: xor a ; init checksum ld (chksum),a ; for use soon call rdrin ; get a char from the rdr device cp etx ; maybe he is just nervous jp z,getrec ; so wait - questionable situation cp soh ; better be an soh jp nz,comfail ; else abort the protocol ; get the terminals record number call rdrin ; get record number ld (trmrno),a ; and save it for later ack/nak branch call sumupd ; and start checksumming ; set up to fill a buffer ld a,00h ; zero to char count ld (charcnt),a ; for index pointer ld (charcnt+1),a ; both halves must get cleared ld (seeneot),a ; and reset the eot status byte ld hl,buffer ; get address of comm buffer ; then read data until etx getr1: ; mainloop push hl ; save the buffer pointer call rdrin ; and get a char pop hl ; restore buffer pointer cp etx ; see if its the end of record jp z,getetx ; so go get checksum if so cp eot ; test for eot jp z,geteot ; and handle if recieved getr2: cp dle ; was it a masking char? jr nz,getr3 ; regular unmasked character push hl ; else get next char call rdrin ; from terminal pop hl ; restore buffer pointer and 03fh ; and correct for masking getr3: ld (hl),a ; save in buffer inc hl ; update pointer call sumupd ; update checksum ld bc,(charcnt) ; update count inc bc ; to reflect chars in buffer ld (charcnt),bc ; merrily counting jp getr1 ; and go back for more ; ; here when eot is spotted geteot: ld (seeneot),a ; set eot recieved flag call sumupd ; update the checksum for eot jp getr1 ; and return to loop for etx, chksum ; recvd an etx getetx: call rdrin ; get term's checksum cp dle ; see if its masked jr nz,getet1 ; and bypass this if not call rdrin ; get real checksum and 01fh ; and make it a control char ; validate the transmission getet1: ld c,a ; and test to see ld a,(chksum) ; that all is ok cp c ; zero if equal jp nz,getnak ; reject if not ld a,(trmrno) ; get term record number ld c,a ; and save for compare ld a,(currec) ; get what host thinks is current sub c ; and test for terminal high jp c,comfail ; signal communications failure if so ld (trmrno),a ; else save a flag for disk write routine call updrnum ; everything looks ok - we are acking xor a ; so clear carry flag to show all went well ret ; and return ; error has occured in xmission getnak: call sendnak ; something is very wrong- send a nak scf ; set the carry flag ret ; and retry ; transmission control variables trmrno: defb 00h ; area for term. record number seeneot: defb 00h ; flag to indicate eot detected charcnt: defw 00h ; counter for chars received ; ; ; ;************************************************************************** ; Routine to write the approved characters to disk. only error is no space ; write a record to the disk a character at a time.. putrec: ld a,(trmrno) ; get flag for record number or a jp nz,dputfin ; bypass put unless correct record ld hl,buffer ; get start of comm record ld bc,(charcnt) ; and get count of chars ld a,b ; and test for zero error or c ; jp z,dputfin ; bypass putloop if so ; dputlp: ld a,(hl) ; get the char push hl ; save the buffer pointer push bc ; save the count call ptchar ; put 1 char to disk stream pop bc ; restore count pop hl ; restore buffer pointer inc hl ; update ptr dec bc ; and update count ld a,b ; test for zero or c ; value in byte counter jp nz,dputlp ; and spin till done ; dputfin: ld a,(dskerr) ; test for possible disk error or a ; should be zero ret z ; ret good if so scf ; else set error for disk space ret ; and return ; ; initialize the disk buffer on startup or after a write dskinit: ld hl,dbuff ; start addr ld de,dbuff+1 ; for overlapping move ld bc,buffend-dbuff-1 ; buffer length-1 ld a,ctlz ; ctlz to clear with ld (hl),a ; save the seed ldir ; and clear ret ; to caller ; ; routine to put a character in the disk buffer and write if buffer is full ; writes will ONLY occur on eot or full buffer ptchar: ld hl,(dpointr) ; get current pointer ld (hl),a ; and save character inc hl ; point to next ld (dpointr),hl ; and save it ld de,buffend ; get limit xor a ; clear carry sbc hl,de ; test for end ret nz ; return if not boundry call ptitout ; write the record ld hl,dbuff ; re-init pointers ld (dpointr),hl ; for next pass call dskinit ; re-init buffer ret ; and return ptitout: ld de,dbuff ; get dma addr ld c,fn$std ; set dmaadr function call bdos ; to os ld de,deffcb ; fcbaddr ld c,fn$wts ; write sequential function call bdos ; and its done ld (dskerr),a ; save possible error status ret ; and return to caller ; ; close the file and write record if non-empty fclose: ld hl,(dpointr) ; get pointer value ld de,dbuff ; and init value xor a ; clear carry sbc hl,de ; is pointer at start of buffer?? jr z,fclos1 ; yes, bypass flush call ptitout ; close the file and rename it to the originally specified name fclos1: ld de,deffcb ; for file close function ld c,fn$cls ; the aforementioned function call bdos ; close and go ld de,deffcb+16 ; get next 16 for rename setup xor a ; clear drive byte inc de ld (de),a ; for later ld hl,deffcb+1 ; and point to old name ld bc,08h ; length for move ldir ; move in file name ld hl,typsav ; get original file type ld bc,03h ; and length ldir ; and move it in too ld c,fn$ren ; rename function change fil.$$$ to fil.ext ld de,deffcb ; addr of fcb for renamed file call bdos ; rename it ret ; to caller ; dskerr: defb 00h ; ;************************************************************************* ; Communications have failed - reset everything and split ; comfail: ld a,knak ; send physical abort character call punout ; and abort ld de,failmes ; get comm failure message call prnmes ; send message ld de,deffcb ; and delete any file by that name ld c,fn$del ; delete function call bdos ; go out in the best way jp cldboot ; and abort ; failmes: defb CR,LF,' Communications Failure - Upload aborted','$' ;**********************************************8 ; Host is out of disk space dspacen: ld a,knak ; send physical abort character call punout ; and abort ld de,dspcmes ; get comm failure message call prnmes ; send message jp cldboot ; and abort ; dspcmes: defb cr,lf,' Host out of disk space - Upload aborted','$' ; ;************************************************************************** ; EOF - send a good eot message to let host know we are done puteot: ld a,0ffh ; turn of the switch to insure ld (masking),a ; that eot is sent unmasked ; ld hl,eotmes ; get addr of eot char ld b,1 ; setup call prmesout ; and send it ret complete: ld a,so ; turn off protocol mode at terminal call punout ; now ld de,ucommes ; get upload complete call prnmes ; send it ; ret ucommes: defb cr,lf,' UPLOAD COMPLETE ','$' eotmes: defb eot ;********************************************************************** ; Wait for an ack from the terminal waitack: call pcharin ; get protocol char cp '.' ; is it ack jp z,gotack ; then handle cp '/' ; is it nak? jp z,rexmit ; then retransmit cp knak ; check for abort jp nz,waitack ; else loop ; ld a,01 ; set nz, clear carry or a ; and return ret ; rexmit: scf ; return carry set ret ; gotack: call updrnum ; update current record number xor a ; set zero flag and clear carry ret ;************************************************************************** ; update current record number updrnum: ld a,(currec) ; get current record number inc a ; and increment cp '9'+1 ; test for overflow jr c,updrok ; still valid if carry ld a,'0' ; else change it updrok: ld (currec),a ; and save result ret ; then return ;**************************************************************************** ; START OF IO ROUTINES - THESE ROUTINES MAY BE MODIFIED AS REQUIRED TO SUPPORT ; THE USERS HARDWARE ENVIRONMENT ;**************************************************************************** ; send a message to terminal using print string convention - this routine ; assumes terminal is accessible as console and uses bdos prnmes: ld c,prnstg ; settup function number call bdos ; call the operating system ret ; and return ;*************************************************************************** ; this routine reads a standard console buffer from the operator- again, using ; bdos rdcon: ld c,rdcbuf ; read console buffer function call bdos ; to os ret ; to caller ;*********************************************************************** ; This routine uses the bios punch call to access the console port ; the routine must send the char in a to the modem without stripping parity ; punout: push af ; save char ld c,a ; get char in proper register ld de,punch ; get offset call biosvct ; go doit pop af ; restore char ret ;************************************************************************** ; This routine calls the bios reader input to get an 8 bit character ; character is returned in a with parity bit INTACT! rdrin: ld de,reader ; get proper offset call biosvct ; go get the char cp knak ; see it its knak jp z,comfail ; comm failure if so ret ; ; ************************************************************************* ; read one char from modem - parity may be stripped pcharin: ld de,conin ; get 1 char via bios call biosvct ; and return ret ; to caller ; currec: defb '1' ; initial record number chksum: defb 00h ; initial check sum dpointr: defw dbuff ; initial pointer value ; ; dbuff: ds 128 ; dma address buffend equ $ buffer equ $ end