$TITLE ('KERMIT - MAIN MODULE') kermit: /* COPYRIGHT (C) 1985, Trustees of Columbia University in the City of New */ /* York. Permission is granted to any individual or institution to use, */ /* copy, or redistribute this software so long as it is not sold for */ /* profit, provided this copyright notice is retained. /* /* Contains the following public routines: */ /* cmdtail, exhelp, ioinit, newline, nin, nout, print, procbaud, spin, */ /* strcmp, synerr, token, upcase, and varcmp */ /* NOTE: See the comment below regarding the default "escape" character */ do; declare true literally '0FFH'; declare false literally '00H'; /* UART control bits */ declare port1cmd literally '0F5H'; declare port1dat literally '0F4H'; declare port1clk literally '0F0H'; declare timing1 literally '036H'; declare port2cmd literally '0F7H'; declare port2dat literally '0F6H'; declare port2clk literally '0F1H'; declare timing2 literally '076H'; declare modesel literally '0F3H'; declare reset literally '040H'; declare EnaTxRx literally '025H'; declare tx$rdy literally '01H'; declare rx$rdy literally '02H'; declare null literally '000H'; declare lf literally '0AH'; declare cr literally '0DH'; declare crlf literally 'cr,lf,null'; declare space literally '20H'; declare dollar literally '24H'; declare bel literally '07H'; declare buflen literally '122'; declare buffer(buflen) byte; declare (cmdstr, temp, cmdptr) address; declare taking byte public initial(false); /* within "TAKE" file */ declare continue byte initial(true); /* ****** Default mode settings ****** */ /* Note that the default setting for the Kermit "escape" character */ /* differs from common Kermit usage. This is due to the fact that */ /* some Intel workstations (e.g. Series III) cannot generate a */ /* "control-right bracket" character. If you are using only Series */ /* IV workstations, you can change the initial value below to suit, */ /* for example, to 29 for control-right bracket. Alternately, you */ /* can use the "SET ESCAPE" command (manually or in an .INI file) */ /* to alter the escape character each time you invoke Kermit. */ declare escchar byte public initial(29); /* control-right bracket */ declare baudrate address public initial(2400); /* port baud rate */ declare parity byte public initial(0); /* parity */ /* (0 = None, 1 = Mark, 2 = Space, 3 = Even, 4 = Odd) */ declare stopbits byte public initial(2); /* number of stop bits */ /* (0 = 1 stop bit, 1 = 1 1/2 stop bits, 2 = 2 stop bits) */ declare port byte public initial(2); /* communications port */ declare maxtry byte public initial(5); /* number of retries */ declare debug byte public initial(false); declare halfduplex byte public initial(false); declare warning$flag byte public initial(false); /* dup-named file switch */ declare take$echo byte public initial(false); /* echo take commands switch */ declare def$drive(5) byte public initial(0,0,0,0,0); /* default drive */ declare prompt(20) byte public initial('ISIS-Kermit>',null); /* Kerm. prompt */ declare def$prompt(20) byte public initial('ISIS-Kermit>',null); /* Default */ declare filename address public; declare state byte public; declare msgnum byte public; declare tries byte public; declare oldtry byte public; /* Masks for comm input and output bytes */ declare input$and byte public; declare output$and byte public; declare output$or byte public; declare cmd byte; declare speed byte; /* Kermit parameter defaults */ declare pksize literally '94'; declare mytime literally '5'; declare mynumpads literally '0'; declare mypadchr literally '0'; declare myeol literally 'cr'; declare myquote literally '023H'; /* Current Kermit parameters */ declare spsize byte public initial(pksize); /* present packet size */ declare timeint byte public initial(mytime); /* present time out */ declare numpads byte public initial(mynumpads); /* how many pads to send */ declare padchar byte public initial(mypadchr); /* present pad character */ declare eol byte public initial(myeol); /* present eol character */ declare quote byte public initial(myquote); /* present quote character */ /* Subroutine declarations */ co: procedure(char)external; declare char byte; end co; ci: procedure byte external; end ci; getc: procedure (port) byte external; declare port byte; end getc; ready: procedure (port) byte external; declare port byte; end ready ; read: procedure(jfn, buf, max, count, status)external; declare(jfn, buf, max, count, status)address; end read; error: procedure(errnum)external; declare(errnum)address; end error; bye: procedure external; end bye; connect: procedure external; end connect; cwd: procedure external; end cwd; exit: procedure external; end exit; finish: procedure external; end finish; get: procedure external; end get; help: procedure external; end help; logout: procedure external; end logout; recv: procedure external; end recv; send: procedure external; end send; set: procedure external; end set; show: procedure external; end show; take: procedure external; end take; takeline: procedure (addr) external; declare addr address; end takeline; takeini: procedure external; end takeini; newline: procedure public; call co(cr); call co(lf); end newline; /* SPIN: Searches a string for a character greater than blank */ spin: procedure(string)address public; declare string address; declare char based string byte; do while (char <> null) and (char < 021H); string = string + 1; end; return string; end spin; strcmp: procedure(s1,s2)byte public; declare(s1,s2)address; declare c1 based s1 byte; declare c2 based s2 byte; declare retval byte; retval = 0; s1 = spin(s1); s2 = spin(s2); if not(c1 = c2) then retval = c1 - c2; do while (c1 > 0) and (c2 > 0) and (retval=0); retval = c1 - c2; s1 = s1+1; s2 = s2+1; end; return retval; end strcmp; /* varcmp: Compare two variable length strings */ /* This routine compares corresponding characters in two strings. */ /* If it finds a null in the first string, it returns the number */ /* of characters which matched, excluding the null. If it finds */ /* a null in the second string or a mismatch, it returns a zero. */ varcmp: procedure(s1,s2)byte public; declare(s1,s2)address; declare c1 based s1 byte; declare c2 based s2 byte; declare cnt byte; cnt = 0; /* s1 = spin(s1); */ /* s2 = spin(s2); */ do while c1 > 0; if c2 = 0 then return 0; if c1 <> c2 then return 0; s1 = s1+1; s2 = s2+1; cnt = cnt + 1; end; return cnt; end varcmp; /* TOKEN: returns a pointer to a null-terminated token pointed */ /* to prior to the call by cmdptr. After the call, cmdptr points */ /* to the end of the original string, or the first character after */ /* the null character replacing the first whitespace after the first */ /* token. */ token: procedure address public; declare result address; declare char based cmdptr byte; result = 0; cmdptr = spin(cmdptr); if char <> null then do; result = cmdptr; do while char > ' '; cmdptr = cmdptr + 1; end; if char <> null then do; char = null; cmdptr = cmdptr + 1; end; end; return result; end token; /* CMDTAIL: returns a pointer to the first nonblank character of the */ /* remainder of the command line, or zero if there were no more */ /* nonblank characters in the command line. */ cmdtail: procedure address public; declare char based cmdptr byte; cmdptr = spin(cmdptr); if char = null then return 0; else return cmdptr; end cmdtail; nout: procedure(n) public; declare n address; declare (quotient, digit) address; declare numbuf(8) byte; declare index byte; if n = 0 then do; call co('0'); return; end; index = 1; do while (n > 0); digit = n mod 10; numbuf(index) = digit + '0'; index = index + 1; n = n / 10; end; do while ((index := index - 1) > 0); call co(numbuf(index)); end; end nout; nin: procedure(string) address public; declare string address; declare result address; declare c based string byte; result = 0; if (string <> 0) then do; string = spin(string); do while (c >= '0') and (c <= '9'); result = result * 10 + (c - '0'); string = string + 1; end; end; return result; end nin; print: procedure(msg) public; declare msg address; declare c based msg byte; do while (c > 0) and (c <> '$'); if c = '\' then call newline; else call co(c); msg = msg + 1; end; end print; synerr: procedure public; call print(.('Syntax error\$')); end synerr; /* IOINIT: This routine takes a port number, 0,1 or 2, and a speed in the */ /* range 0-8 and initializes the required port to work at the required */ /* speed. The routine returns no parameters. */ ioinit: procedure public; declare baud structure (code0(9) byte, code1(9) byte, mult(9) byte) /* Low-order byte of counter values */ data (0BAH, 80H, 40H, 20H, 10H, 20H, 10H, 08H, 04H, /* High-order byte of counter values */ 02H, 0H, 0H, 0H, 0H, 0H, 0H, 0H, 0H, /* 8251A command byte baud rate multiplier control bits */ 02H, 03H, 03H, 03H, 03H, 02H, 02H, 02H, 02H); /* 8251A command byte parity and length control bits */ /* (0=None, 1=Mark, 2=Space, 3=Even, 4=Odd) */ declare paritymask(5) byte data (0CH, 0CH, 0CH, 38H, 18H); /* 8251A command byte stop bits control bits */ /* (0 = 1 stop bit, 1 = 1 1/2 stop bits, 2 = 2 stop bits) */ declare stopmask(3) byte data (40H, 80H, 0C0H); /* Mask bytes for comm. input and output bytes */ declare inp$mask$and(9) byte data (0FFH, 7FH, 0FFH, 7FH, 7FH); declare out$mask$and(9) byte data (0FFH, 0FFH, 07FH, 0FFH, 0FFH); declare out$mask$or(9) byte data (0H, 80H, 0H, 0H, 0H); declare (c, status) byte; if debug then call print(.('\initializing serial port\$')); do case port; do; if debug then call print(.('port 0 initialized\$')); end; do; if debug then call print(.('port 1 initialized\$')); /* Put the USART into a known state by writing */ /* three zero command bytes to it */ output(port1cmd) = 0H; output(port1cmd) = 0H; output(port1cmd) = 0H; /* Reset the USART */ output(port1cmd) = reset; output(modesel) = timing1; output(port1clk) = baud.code0(speed); output(port1clk) = baud.code1(speed); output(port1cmd) = (stopmask(stopbits) or paritymask(parity) or baud.mult(speed)); input$and = inp$mask$and(parity); output$and = out$mask$and(parity); output$or = out$mask$or(parity); if debug then do; call print(.('Mode command: $')); call nout(stopmask(stopbits) or paritymask(parity) or baud.mult(speed)); call newline; end; output(port1cmd) = EnaTxRx; if ready(1) > 0 then c = getc(1); /* discard any char */ end; do; if debug then call print(.('port 2 initialized\$')); /* Put the USART into a known state by writing */ /* three zero command bytes to it */ output(port2cmd) = 0H; output(port2cmd) = 0H; output(port2cmd) = 0H; /* Reset the USART */ output(port2cmd) = reset; output(modesel) = timing2; output(port2clk) = baud.code0(speed); output(port2clk) = baud.code1(speed); output(port2cmd) = (stopmask(stopbits) or paritymask(parity) or baud.mult(speed)); input$and = inp$mask$and(parity); output$and = out$mask$and(parity); output$or = out$mask$or(parity); if debug then do; call print(.('Mode command: $')); call nout(stopmask(stopbits) or paritymask(parity) or baud.mult(speed)); call newline; end; output(port2cmd) = EnaTxRx; if ready(2) > 0 then c = getc(2); /* discard any char */ end; end; end ioinit; usage: procedure; call print(.('usage: kermit (110|150|300|600|1200|2400|4800|9600$')); call print(.('|19200) (1|2)\$')); call exit; end usage; procbaud: procedure (newbaud) byte public; declare rate(9) address data(110, 150, 300, 600, 1200, 2400, 4800, 9600, 19200); declare (result, i, done) byte; declare newbaud address; result = false; /* tentative result of "bad rate" */ i = 0; done = false; do while (done = false); if i > 8 then done = true; /* off end of table */ else if newbaud < rate(i) then done = true; /* not found */ else if newbaud = rate(i) then do; done = true; result = true; end; else i = i + 1; /* try next */ end; if result = true then do; baudrate = newbaud; speed = i; end; return result; end procbaud; readln: procedure; declare (count, status) address; call read(1, .buffer, buflen, .count, .status); if status > 0 then do; call print(.('READLN FAILED\$')); call error(status); call exit; end; buffer(count-2) = 0; cmdptr = .buffer; end readln; /* Convert the contents of a string from lower case to upper case */ upcase: procedure(addr) public; declare addr address; declare chr based addr byte; declare capdiff byte; capdiff = 'a' - 'A'; do while chr <> 0; if (chr >= 'a') and (chr <= 'z') then chr = chr - capdiff; addr = addr + 1; end; end upcase; /* Display help for the EXIT command */ exhelp: procedure public; call print(.('\EXIT\\$')); call print(.(' The EXIT command causes KERMIT to terminate and $')); call print(.('return to ISIS.\\$')); call print(.('Syntax:\\$')); call print(.(' EXIT\\$')); end exhelp; /* Get the next command to be executed */ getcmd: procedure; cmdstr = 0; do while (cmdstr = 0); if taking then do; /* Get the command from the "TAKE" file */ call takeline(.buffer); cmdptr = .buffer; if takeecho and taking then do; /* (Takeline will have turned "taking" off at EOF) */ call print(.('TAKE command>$')); call print(cmdptr); call newline; end; end; else do; /* Get the command from the console */ call print(.prompt); /* Display the prompt */ call readln; end; cmdstr = token; /* Get the command "word" */ call upcase(cmdstr); /* Convert to upper case */ end; end getcmd; /* Execute a Kermit command */ docmd: procedure; if (varcmp(cmdstr,.('BYE',null)) >= 1) then cmd = 1; else if (varcmp(cmdstr,.('CONNECT',null)) >= 2) then cmd = 2; else if (varcmp(cmdstr,.('CWD',null)) >= 2) then cmd = 3; else if (varcmp(cmdstr,.('EXIT',null)) >= 1) then cmd = 4; else if (varcmp(cmdstr,.('FINISH',null)) >= 1) then cmd = 5; else if (varcmp(cmdstr,.('GET',null)) >= 1) then cmd = 6; else if (varcmp(cmdstr,.('HELP',null)) >= 1) then cmd = 7; else if (varcmp(cmdstr,.('LOGOUT',null)) >= 1) then cmd = 8; else if (varcmp(cmdstr,.('RECEIVE',null)) >= 1) then cmd = 9; else if (varcmp(cmdstr,.('SEND',null)) >= 3) then cmd = 10; else if (varcmp(cmdstr,.('SET',null)) >= 3) then cmd = 11; else if (varcmp(cmdstr,.('SHOW',null)) >= 2) then cmd = 12; else if (varcmp(cmdstr,.('TAKE',null)) >= 1) then cmd = 13; else do; call print(.('Invalid or ambiguous command name\$')); cmd = 0; end; if (cmd = 1 or cmd = 2 or cmd = 4 or cmd = 5 or cmd = 8 or cmd = 12) then if token > 0 then /* Only some commands can have operands */ do; call print(.('Extraneous operand(s) specified\$')); cmd = 0; end; do case cmd; /* 0 = Command error previously diagnosed */ do; /* Null action */ end; /* 1 = BYE command */ do; call bye; continue = false; end; /* 2 = CONNECT command */ call connect; /* 3 = CWD command */ call cwd; /* 4 = EXIT command */ continue = false; /* 5 = FINISH command */ call finish; /* 6 = GET command */ call get; /* 7 = HELP command */ call help; /* 8 = LOGOUT command */ call logout; /* 9 = RECEIVE command */ call recv; /* 10 = SEND command */ call send; /* 11 = SET command */ call set; /* 12 = SHOW command */ call show; /* 13 = TAKE command */ call take; end; end docmd; /* *** main program *** */ /* Fetch the command line arguments */ call readln; /* Read desired baud rate, if supplied */ temp = token; if temp > 0 then baudrate = nin(temp); /* Get desired port, if supplied */ temp = token; if temp > 0 then port = nin(temp); /* Check for garbage on the end of the line */ if token > 0 then call usage; if (port < 1) or (port > 2) then call usage; if (procbaud(baudrate) = false) then call usage; /* bad baudrate */ call ioinit; /* Initialize a "TAKE" condition from "KERMIT.INI" if it exists */ call takeini; if not taking then do; /* The "INI" file may change the port and/or baud rate */ call print(.('Serial port $')); call nout(port); call print(.(', Baud rate $')); call nout(baudrate); call newline; end; do while (continue); call getcmd; /* Get the next command line */ call docmd; /* Execute the command */ end; call exit; end kermit;