# /* Talk to devices connected to serial lines. Specialized to load all * kinds of PDP-11's on the other end of those lines. * * Uses two processes: one (the parent) reads from the keyboard (standard in) * and writes to the line; the other (the child) reads from the line, and writes * to the display (standard out). A pipe allows communication between the two: a * a signal tells the partner to read the message from the pipe. * * The -t argument says the CPU on the other end has ODT (default). * The -e argument says the CPU on the other end has a console emulator. * The -s argument says the CPU has only a bootloader of some kind. * The -m nnn argument sets the memory size (for the bootloader) to nnn. * The -b nnn argument sets the baud rate on the line to nnn (default 9600). * The -h nnn argument sets the length of the BREAK to halt the computer * on the line to nnn. * The -l nnn argument sets the length of the time to wait for the console * to catch up to nnn. * The -c flag says to use conservative dallying when talking to ODT. * The -o flag prints the incoming characters in numeric form (base 10). * The -d flag prints debugging info. Given more than once, it produces * more debugging info. */ #include "/lib/h/stat.h" #include "/lib/h/sgtty.h" #include "/lib/h/sig.h" #define MAGICSTOP 0123456 /* For internal scripts, end marker */ /* Boot loader. * * Starting address for various memory sizes. (I know, this could be calculated * - I'm too lazy to work out how.) * * One also has to change the word at LOC: to have the same top 4 bits as the * boot loader start address. */ #define BOOTDIV 8 /* Units of table increment */ #define BOOTMAX 7 /* Largest table index allowed */ char *bootaddr[] { 0017744, /* 8KB */ 0037744, /* 16KB */ 0057744, /* 24KB */ 0077744, /* 32KB */ 0117744, /* 40KB */ 0137744, /* 48KB */ 0157744 /* 56KB */ }; char *bootloc 0157744; /* Default to 56KB */ #define BOOTMOD 9 /* Index of LOC: to modify */ #define BOOTMASK 0177400 /* Mask to produce patch value */ char *bootldr[] { 0016701, /* MOV CSR, R1 */ 0000026, 0012702, /* LOOP: MOV #OFFSET, R2 */ 0000352, /* OFFSET: */ 0005211, /* INC @R1 - Start device */ 0105711, /* TSTB @R1 - Wait for ready */ 0100376, /* BPL .-2 */ 0116162, /* MOVB 2(R1), LOC(R2) - Store byte */ 0000002, 0157400, /* LOC: */ 0005267, /* INC OFFSET */ 0177756, 0000765, /* BR LOOP */ 0177560, /* CSR: CONSOLE */ MAGICSTOP }; /* Vector area */ char *vecarea[] { 000777, /* Loop at zero if we wind up there */ 000000, 000006, /* NXM */ 000000, /* Halt at 6 */ 000012, /* Illegal instruction */ 000000, /* Etc */ MAGICSTOP }; /* Unix stuff */ #define INTCH 3 /* Interrupt char - can't get this from */ /* the kernel, grrr. */ #define IFILE 0 #define OFILE 1 extern int errno; extern char *sys_errlist[]; struct sgargs tsargs, lsargs; int lmode; struct inode iino; /* Command stuff */ char cputyp 't'; /* Does CPU have ODT, or what? */ int oflg; /* Dump binary data */ int cflg; /* Conservative dallying */ int dflg; /* Debugging */ int iflg; /* Assume bootloader there */ int brkdly 1; /* How long to make break */ int condly 3; /* How long to wait for console to catch up */ int bauda 9600; /* Baud rate */ int baudn; int tifile, tofile; int lifile, lofile; int tmode 0; /* TTY mode set */ #define SIGCOMM 19 /* Signal that pipe contains a message */ int comms[2]; /* Pipe for IPC between the processes */ int parent, child; /* PIDs */ /* Data */ char alnosw[] "/lib/absldr-ns"; /* Version with no CSW */ char alnohlt[] "/lib/absldr-nsnh"; /* Version that doesn't halt */ #define BLKSIZ 512 /* Input buffer */ char ibuf[BLKSIZ]; char *ibp; char *lst; int bfile; #define TTYNM 8 /* Index for line name */ char tname[] "/dev/ttyX"; #define FILENMSZ 128 char dfname[FILENMSZ]; /* File to be downloaded */ #define SCSZ 64 char scriptln [SCSZ]; /* Line in deposit script file */ #define NUMSIZ 6 char obuf[NUMSIZ]; /* Octal output buffer */ /* Error strings used in multiple places */ char lineprob[] "Problem writing line"; /* Read args, set up ttys, set up IPC pipe, fork (one process for each * direction), then each falls into copy loop. */ main(argc, argv) char **argv; { int cleanup(), incoming(); char *arg; if (argc < 2) helpexit(); argv++; for (argc--; (argc > 0); argc--) { arg = *argv; if (*arg++ != '-') break; argv++; switch (*arg) { case '?': helpexit(); case 'm': fixloc(atoi(*argv++)); argc--; break; case 'b': bauda = atoi(*argv++); argc--; break; case 'h': brkdly = atoi(*argv++); argc--; break; case 'l': condly = atoi(*argv++); argc--; break; case 'o': oflg++; break; case 'c': cflg++; break; case 't': cputyp = 't'; break; case 'e': cputyp = 'e'; condly = 5; break; case 's': cputyp = 's'; break; case 'd': dflg++; break; default: printf("Bad flag: %c\n", *arg); exit(1); } } setbaud(); if (argc != 1) { printf("No tty line specified\n"); exit(1); } tname[TTYNM] = **argv; if (dflg) printf("Line: '%s' '%s'\n", *argv, &tname[0]); lifile = open(&tname[0], 0); lofile = open(&tname[0], 1); if ((lifile < 0) || (lofile < 0)) { printf("Terminal line '%c': ", *argv); serror("Can't open device line"); } if (pipe(&comms[0]) < 0) serror("Can't open pipe for IPC"); parent = getpid(); signal(SIGINT, &cleanup); signal(SIGQIT, &cleanup); tifile = IFILE; tofile = OFILE; ttyset(tifile, &tsargs); child = fork(); if (child == -1) serror("Can't fork partner process"); if (child != 0) readtty(); else { signal(SIGCOMM, &incoming); ttyset(lifile, &lsargs); readline(); } lerror("Unexpected return from loop"); } /* Update load location to put absolute loader at, depending on amount * of memory. */ fixloc(size) { char *ldpnt; if ((size % BOOTDIV) != 0) { printf("Memory size must be multiple of 8KB\n"); exit(1); } size =/ BOOTDIV; if (--size >= BOOTMAX) { printf("Memory size must be less than 56KB\n"); exit(1); } ldpnt = bootaddr[size]; bootloc = ldpnt; bootldr[BOOTMOD] = (ldpnt & BOOTMASK); if (dflg) printf("Load loc: %d %o %o\n", size, bootloc, bootldr[BOOTMOD]); } /* Set up baud rate for line. */ setbaud() { struct ttyspd *tp; int i; tp = &ttyspds[0]; for (i = 0; (i < NBAUDS); i++) { if (tp->t_nspd != bauda) { tp++; continue; } baudn = tp->t_uspd; if (dflg) printf("Baud rate: %d %d\n", bauda, baudn); break; } if (i >= NBAUDS) { printf("Baud rate %d not recognized\n", bauda); exit(1); } } /* Routines run in the keyboard->line process */ readtty() { char rchar; int rd; for (;;) { rd = read(tifile, &rchar, 1); if (rd == 0) continue; if (rd == -1) serror("Problem reading keyboard"); if (dflg > 3) printf("Wt %d '%c'\r\n", rchar, rchar); if (rchar == '') { docmd(); continue; } cwrite(lofile, &rchar, 1, "Problem writing device line"); } } docmd() { int rd, wt; char rchar; for (;;) { cwrite(tofile, "\r\nCmd: ", 7, "Command prompt"); rd = read(tifile, &rchar, 1); cwrite(tofile, "\r\n", 2, "Spacer"); if (rd == 0) continue; if (rd == -1) serror("Problem reading keyboard command"); switch (rchar) { case '\n': case '\r': return; case 'q': case 'x': shutdown(); break; case 'b': if (cputyp == 't') sendbrk(); break; case 'i': if (iflg) { cwrite(tofile, "\r\nBoot bad\r\n", 12, "Boot status"); iflg = 0; } else { cwrite(tofile, "\r\nBoot OK\r\n", 11, "Boot status"); iflg++; } break; case 'r': loadbin(); break; case 'l': loadfile(); break; case 's': loadscript(); break; case '?': printf("q/x - exit\n\r"); printf("b - send BREAK\n\r"); printf("i - toggle bootloader OK\n\r"); printf("r - load binary file\n\r"); printf("l - load LDA file\n\r"); printf("s - load console script\n\r"); break; default: printf("Bad command '%c'\r\n", rchar); return; } return; } } shutdown() { cwrite(comms[1], "q", 1, "Shutdown write"); kill(child, SIGCOMM); ttyunset(tifile, &tsargs); printf("\n"); exit(0); } /* Load a 'deposit script' file; format is "[loc] [contents]" followed by " * [contents]" until a line with a '.' end the script; by special dispensation * "[loc]" may be of the form "Rn" on machines which support register access. * An optional ending line with 'xxxG' starts the program at 'xxx'. Blank * lines are ignored. */ loadscript() { char *getfnm(); char *fnm, *errstr; int lfile, fsiz, dcode; if (cputyp == 's') { printf("Scripting operations not supported\r\n"); return; } fnm = getfnm("Script"); if (fnm == 0) return; lfile = openfile(fnm); if (lfile < 0) return; fsiz = iino.i_size1; if (dflg) printf("Loading script file '%s' %d %d\r\n", fnm, lfile, fsiz); else printf("Loading script file '%s'\r\n", fnm); errstr = "Problem writing script line"; dcode = doscript(errstr); if (dflg) printf("\r\ndoscript done %d\r\n", dcode); closefile(lfile); if (dcode == 'X') return; if (cputyp == 't') { cwrite(lofile, "\r", 1, errstr); dally(1); } if (dcode == '.') { printf("\r\nLoading '%s' complete\r\n", fnm); return; } if ((dcode <= 0) || (dcode > 7)) lerror("Bad return code from doscript"); printf("\r\nStarting '%s' at '%s'\r\n", fnm, &scriptln[0]); if (cputyp == 'e') cwrite(lofile, "L ", 2, errstr); cwrite(lofile, &scriptln[0], dcode, errstr); if (cputyp == 't') { cwrite(lofile, "G", 1, errstr); return; } cwrite(lofile, "\r", 1, errstr); dally(1); cwrite(lofile, "S\r", 2, errstr); } /* Loop over input file, reading lines. Returns code to tell caller whether * to simply close block, do a 'go' command, or if there was an error. * * Sleeps are because until the 11 has finished typing the typeout in response * to '/', etc it cannot process typein, so leading characters will be lost. */ doscript(errstr) char *errstr; { int inblk; int rsiz, lsiz, dsiz; register char *cp; char *data; inblk = 0; for (;;) { rsiz = rdline(&scriptln[0], SCSZ); if (dflg) printf("\r\nrdline '%s' %d\r\n", &scriptln[0], rsiz); cp = &scriptln[0]; if (*cp == '\n') continue; if (*cp == '.') return('.'); lsiz = 0; while ((cp < &scriptln[SCSZ]) && (*cp != ' ')) { if (*cp == 'G') { /* Gnash; no multi-level break */ *cp = 0; return(lsiz); } if ((*cp == 'R') && (cputyp != 't')) { printf("Register load not supported by console emulator\r\n"); return('X'); } if (((*cp < '0') || (*cp > '7')) && ((*cp != 'R') || (lsiz != 0))) { printf("Bad location '%s' in script\n\r", &scriptln[0]); return('X'); } lsiz++; cp++; } *cp++ = 0; if (lsiz > 0) { if (dflg > 1) printf("Loc: '%s'\r\n", &scriptln[0]); if ((inblk != 0) && (cputyp == 't')) { cwrite(lofile, "\r", 1, errstr); dally(0); } inblk = 0; if (cputyp == 'e') cwrite(lofile, "L ", 2, errstr); cwrite(lofile, &scriptln[0], lsiz, errstr); dally(0); if (cputyp == 't') cwrite(lofile, "/", 1, errstr); else cwrite(lofile, "\r", 1, errstr); dally(1); } dsiz = 0; data = cp; while ((cp < &scriptln[SCSZ]) && (*cp != '\n')) { if ((*cp < '0') || (*cp > '7')) { printf("Bad data '%s' in script\r\n", data); return('X'); } dsiz++; cp++; } *cp++ = 0; if (dflg > 1) printf("Dep: '%s'\r\n", data); if (inblk != 0) { if (cputyp == 't') { cwrite(lofile, "\n", 1, errstr); dally(1); } } else inblk++; if (cputyp == 'e') cwrite(lofile, "D ", 2, errstr); cwrite(lofile, data, dsiz, errstr); if (cputyp != 'e') { dally(0); continue; } cwrite(lofile, "\r", 1, errstr); dally(1); } lerror("Loop exit in doscript"); } /* Load a binary file. */ loadbin() { char *fnm; fnm = getfnm("Binary"); if (fnm == 0) return; if (dflg) printf("Loading binary file '%s'\r\n", fnm); readfile(fnm); } /* Load a file in .LDA format; first has to manually toggle in the * bootstrap loader; then uses that to load the absolute loader; * finally can use that to load the .LDA file. */ loadfile() { char *fnm, rchar; int lret, rd; fnm = getfnm("LDA"); if (fnm == 0) return; lret = open(fnm, 0); if (lret < 0) { printf("Cannot open input file '%s'\r\n", fnm); return; } rd = read(lret, &rchar, 1); close(lret); if (rd != 1) { printf("Cannot read input file '%s'\r\n", fnm); return; } if (rchar != 1) { printf("'%s' is not an .LDA file\r\n", fnm); return; } if (cputyp == 't') loadboot(&lineprob[0]); if (cputyp == 'e') startboot(&lineprob[0]); lret = loadabs(&lineprob[0]); if (lret < 0) return; if (dflg) { cwrite(tofile, "\r\nProceed to load: ", 11, "Command prompt"); rd = read(tifile, &rchar, 1); } if (dflg) printf("Loading file '%s'\r\n", fnm); /* else dally(1); */ readfile(fnm); } loadboot(errstr) char *errstr; { if (iflg == 0) { if (dflg) printf("Loading bootloader\r\n"); loadblock(0, &vecarea[0], errstr); loadblock(bootloc, &bootldr[0], errstr); } if (dflg) printf("Starting bootloader\r\n"); oprint(lofile, bootloc); cwrite(lofile, "G", 1, errstr); dally(1); printf("\r\n"); } startboot(errstr) char *errstr; { int odly; printf("Starting bootloader\r\n"); cwrite(lofile, "TT\r", 3, errstr); odly = condly; condly = 90; /* Wait for core to be sized */ dally(1); condly = odly; printf("\r\n"); } loadabs(errstr) char *errstr; { int lret; char *absfile; if (dflg) printf("Loading absloader\r\n"); if (cputyp == 't') absfile = &alnosw[0]; else absfile = &alnohlt[0]; lret = readfile(absfile); if (lret < 0) return(-1); if (cputyp != 't') return; if (dflg) printf("Starting absloader\r\n"); dally(1); dally(1); cwrite(lofile, "P", 1, errstr); return(0); } /* Ditto previous comments about sleeping. */ loadblock(loc, contents, errstr) char *loc, **contents, *errstr; { register char **cp; if (dflg) printf("Loading block at %o\r\n", loc); oprint(lofile, loc); dally(0); cwrite(lofile, "/", 1, errstr); dally(1); cp = contents; while (*cp != MAGICSTOP) { if (dflg > 1) printf("Dep: %o\r\n", *cp); oprint(lofile, *cp++); if (*cp != MAGICSTOP) cwrite(lofile, "\n", 1, errstr); dally(1); } cwrite(lofile, "\r", 1, errstr); dally(0); if (dflg) printf("\r\n"); } /* Hack - doesn't handle files longer than 2^16 bytes. Not a problem, * obviously. */ readfile(fnm) char *fnm; { char c; int dfile, i; dfile = openfile(fnm); if (dfile < 0) return(-1); i = iino.i_size1; /* if (dflg || (strcmp(nm, &alnosw[0]) != 0)) */ printf("Reading file '%s' %d %d\r\n", fnm, dfile, i); while (i > 0) { c = rdch(); if (dflg > 2) printf("Send char %d %o\r\n", i, c); cwrite(lofile, &c, 1, "Sending file"); i--; } closefile(dfile); return(0); } /* Halt processor by sending a break (QBUS machines only) * * Not sure I need to get current stty() modes? Should still be * in sgtty struct? */ sendbrk() { int sval; sval = gtty(lifile, &lsargs); if (sval < 0) serror("Cannot get terminal mode"); lsargs.extend = 'S'; lsargs.mode = 'BR'; if (dflg) printf("Break chan %d: %d %o\n\r", lifile, lsargs.extend, lsargs.mode); sval = stty(lifile, &lsargs); if (sval < 0) serror("Cannot set line mode"); delay(brkdly); lsargs.extend = 'C'; sval = stty(lifile, &lsargs); if (sval < 0) serror("Cannot set line mode"); lsargs.extend = ' '; } /* Utilities; get a filename, open and close files. */ char *getfnm(ftype) char *ftype; { register char *fnm; int rd; char rchar; fnm = &dfname[0]; cwrite(tofile, "\r\n", 2, "File prompt"); cwrite(tofile, ftype, strlen(ftype), "File prompt"); cwrite(tofile, " file: ", 7, "File prompt"); while (fnm < &dfname[FILENMSZ]) { rd = read(tifile, &rchar, 1); if (rd == 0) continue; if (rd == -1) serror("Problem reading file name"); if (rchar == '\b') { cwrite(tofile, &rchar, 1, "Erase character"); if (--fnm < &dfname[0]) { cwrite(tofile, &" ", 1, "Blank character"); fnm = &dfname[0]; } continue; } if (rchar != '\r') { cwrite(tofile, &rchar, 1, "Filename character"); *fnm++ = rchar; continue; } *fnm++ = 0; cwrite(tofile, "\r\n", 2, "Filename termination"); break; } if (fnm >= &dfname[FILENMSZ]) lerror("File name too long"); if (fnm == &dfname[1]) return(0); return(&dfname[0]); } /* Open up a file for reading. * For now, all callers get to share a buffer. * Returns channel no; caller must close when done. * Length is returned in iino.i_size1 (hack - single return only * in C). */ openfile(fnm) char *fnm; { int rfile; rfile = open(fnm, 0); if (rfile < 0) { printf("Cannot open input file '%s'\r\n", fnm); return(-1); } bufinit(rfile); if (stat(fnm, &iino) < 0) serror("Can't stat input file"); return(rfile); } /* Close a file opened for reading when done with it. */ closefile(rfile) int rfile; { close(rfile); bufdone(rfile); } /* Routines run in the line->display process */ readline() { int rd, wt; char rchar; for (;;) { rd = read(lifile, &rchar, 1); if (rd == 0) { if (dflg) printf("Zero line read\r\n"); continue; } if (rd == -1) serror("Problem reading line"); if (dflg > 3) printf("Rd: '%c' %d - ", rchar, rchar); prchar(rchar, "Problem writing tty line"); if (dflg > 3) printf("\r\n"); } } prchar(c, errmsg) char c, *errmsg; { int cx; if (oflg) { cx = (c & 0377); printf("ch: %d", cx); if ((c >= 040) && (c < 0177)) printf(" '%c'", c); printf("\r\n"); return; } if (c == 0177) { cwrite(tofile, "^~", 2, errmsg); return; } if (c == '\r') { cwrite(tofile, "\r", 1, errmsg); return; } if (c == '\n') { cwrite(tofile, "\n", 1, errmsg); return; } if (c < 040) { cwrite(tofile, "^", 1, errmsg); c =+ 0100; } cwrite(tofile, &c, 1, errmsg); /* printf("Bad char in prchar() - '%c' %o", c, c); cleanup(); */ } incoming() { int rd, wt; char rchar; rd = read(comms[0], &rchar, 1); if ((rd == 0) || (rd == -1)) serror("Problem reading channel command"); ttyunset(lifile, &lsargs); switch (rchar) { case 'q': exit(0); default: lerror("Problem reading channel command"); } } /* Service routines to set up lines. */ ttyset(tfile, sargs) struct sgargs *sargs; { int sval; /* if (argc == 0) { tifile = open("/dev/tty", 0); if (tifile < 0) serror("Cannot open terminal"); } if (dflg) printf("terminal files %d %d\n", tifile, tofile); */ sval = gtty(tfile, sargs); if (sval < 0) serror("Cannot get terminal mode"); if (sargs == &lsargs) { lmode = sargs->mode; sargs->ispeed = baudn; sargs->ospeed = baudn; } sargs->mode =& ~(SG_ECHO | SG_CRMOD | SG_LCASE | SG_XTABS); sargs->mode =| SG_RAW; if (dflg) printf("Setup chan %d: %d %d %d %o\n\r", tfile, sargs->ispeed, sargs->ospeed, sargs->erase, sargs->mode); sval = stty(tfile, sargs); if (sval < 0) serror("Cannot set terminal mode"); if (tfile == tifile) tmode++; if (sargs != &lsargs) return; sargs->extend = 'S'; /* sargs->mode = 'LI'; sval = stty(tfile, sargs); if (sval < 0) serror("Cannot set line mode"); */ sargs->mode = 'LO'; sval = stty(tfile, sargs); if (sval < 0) serror("Cannot set line mode"); sargs->extend = ' '; } ttyunset(tfile, sargs) struct sgargs *sargs; { int sval; if (tfile == tifile) { if (tmode <= 0) return; tmode = 0; } if (sargs != &lsargs) { sargs->mode =| (SG_ECHO | SG_CRMOD); sargs->mode =& ~SG_RAW; } else sargs->mode = lmode; sval = stty(tfile, sargs); if (sval < 0) serror("Cannot reset terminal mode"); /* if (tifile != IFILE) close(tifile); */ } /* General utility routines. Checked write, print octal (can't use * printf() since not to terminal), etc. */ cwrite(chan, buf, size, errmsg) { int wt; wt = write(chan, buf, size); if (wt == size) return; serror(errmsg); } oprint(chan, num) { register char *cp; int i, d, wt; cp = &obuf[NUMSIZ]; for (i = NUMSIZ; (i > 0); i--) { d = (num & 07); if (i == 1) d =& 01; *--cp = ('0' + d); num =>> 3; } cwrite(chan, &obuf[0], NUMSIZ, "Problem writing number to channel"); } dally(always) int always; { if (always || cflg) delay(condly); } /* Minimal input buffering package - only one client at a time. */ bufinit(f) int f; { if (bfile != 0) lerror("File buffer already in use"); bfile = f; ibp = &ibuf[BLKSIZ]; lst = ibp; } bufdone(f) int f; { if (bfile != f) lerror("Buffer done on bad file"); bfile = 0; } rdch() { register char *cp; int len; char c; cp = ibp; if (ibp < lst) { c = *cp++; ibp = cp; return(c); } len = read(bfile, &ibuf[0], BLKSIZ); if (len < 0) serror("File read error"); if (dflg) printf("read %d\r\n", len); if (len == 0) return(-1); ibp = &ibuf[0]; lst = &ibuf[len]; return(rdch()); } rdline(abp, maxlen) char *abp; int maxlen; { register char *bp; register int i; int xc; bp = abp; for (i = 0; (i < maxlen); i++) { xc = rdch(); if (xc == -1) lerror("Unexpected EOF on file"); *bp++ = xc; if (xc == '\n') return; } return(i); } /* Clean up tty state when done, various error routines, etc. */ cleanup() { if (getpid() == parent) { cwrite(comms[1], "q", 1, "Shutdown write"); kill(child, SIGCOMM); } ttyunset(tifile, &tsargs); printf("\n"); exit(1); } lerror(errstr) char *errstr; { printf("%s", errstr); cleanup(); } serror(errstr) char *errstr; { char *s; if (errstr != 0) printf("%s: ", errstr); s = sys_errlist[errno]; printf("%s", s); cleanup(); } helpexit() { printf("ttytalk {-t] {-e} {-s} {-m nnn} {-b nnn} {-h nnn} {-l nnn} {-o} {-c} {-d} \n"); }