Google
 

Trailing-Edge - PDP-10 Archives - decuslib10-08 - 43,50512/netio.b36
There are no other files named netio.b36 in the archive.
MODULE NETIO=
!Tops-10 Network device I/O for NETSPL
BEGIN
!
! Table of Contents
!
FORWARD ROUTINE
NDBINI,		!Initialize an NDB
LINK,		!Open a logical link
NODENN,		!Get node number or name
XINPUT,		!Read in a buffer from the network
XOUTPUT,	!Write out...
!XREAD,		!Read a string from net
XWRITE,		!Write a string to net
XBINA,		!Get byte, write thru pointer & increment pointer
XBIN,		!Get a byte & return it
XBOUTA,		!Put byte & increment source byte pointer
XBOUT,		!Put byte out
XMTMSG,		!Output a DAP message
ENTER,		!Enable output to a TSK
TSK;		!Do a TSK. UUO for 7.00 or later

!
! Conditional compilation
!
COMPILETIME FTPSI=0;	!On to trust software interrupt system
COMPILETIME FTTRACE=-1;	!On to allow DAP message tracing

!
! Require & Library files
!
REQUIRE 'INTR.REQ';
LIBRARY 'NODTBL';

!
! Version
!
THIS_IS [NETI]

VERSION [2]		EDIT [22]	DATE [14,DEC,79]
%( 
  Revision History
[22]	Allow control of # of data requests
[21]	Allow # of input & output TSK buffers to be set separately
	Since 7.00 can use lots of input buffers, but only 1 output buffer.
[20]	Use of DC_DAR & DC_DAT was backwards. fixed it.
[17]	Use new stuff for connects also
[16]	Make this work on 7.00 monitor, use TSK. UUO in new routine TSK
[15]	Check for TSK available before attempting to open it
[14]	Make LINK routine impervious to spurious interrupts for active tasks
[13]	Set # of arguments in interrupt block correctly
[12]	More cleanup of bound refs.
[11]	Make LINK routine defend against spurious interrupts
	for passive tasks.

)%
!++
!	Facility:	NETSPL,RMCOPY,NODTBL
!
!Abstract:
!	Subroutines to interface to the network device 'TSK' on TOPS-10
!	These routines do non-blocking I/O to the network.  They interact
!	with the software interrupt system via the SIGNALling mechanism
!	used in NETSPL (modules INT & INTSIG, INTR.REQ, INTR.BLI)
!	They also do NCL segmentation REQUIRING MONITOR MODS TO WORK.
!	If the link word in a buffer header AND 200000,,0 NEQ 0, then
!	you haven't gotten to the end of the record yet.
!	See macros INOEOR & ONOEOR.  These routines set timers according
!	to NDB$CONTO & NDB$REPTO to time out if a timely response is
!	not forthcoming.  If the values are 0, the feature is disabled.

!
!Author: Andrew Nourse
!


!
! Literals
!
LITERAL
	DEFAULT_INNETB=1,	!1 network buffer for input under 6.03
	DEFAULT_ONNETB=1,	!1 network buffer for output
	DEFAULT_CONTO=360,	!Default timeout for connect = 2 minutes
	DEFAULT_REPTO=180;	!Default timeout for message = 1 minute
BUILTIN MACHOP;
LITERAL DEV_AVAIL=%O'40000000',
	DEV_OPENED=%O'200000';
LITERAL	TSK_=%O'177',		!TSK. UUO is this CALLI
	_TKFRS=1,		!Function code for TSK read status
	_TKFEP=2,		!Function code for TSK enter Passive state
	_TKFEA=3,		!Function code for TSK enter Active state
	_TKFEI=4,		!Function code for TSK enter Idle state
	_TKFWT=5,		!Function code for TSK wait for connect
	_TKFOT=6,		!Function code for TSK output
	_TKFIN=7,		!Function code for TSK input

	DC_DAT=1,		!NCL DAP code for data w/o end of record
	DC_DAR=2,		!NCL DAP code for data with end of record

	TS_UDW=%O'14',		!Error code for I/O-type error

	NPDLEN=16;		!Plenty of room in NPD

BIND	LOCAL_PID=UPLIT(%ASCIZ 'FTS[1,2]'),
	LOCAL_PID_LEN=8;

!
! Macros
!

MACRO TSK_INPUT(NB)=
	BEGIN
	LOCAL TASK_BLOCK: VECTOR[3];
	TASK_BLOCK[0]=_TKFIN;
	TSK(NB,3,TASK_BLOCK);
	.TASK_BLOCK[2]	!Return (NCL) DAP code
	END %,

      TSK_OUTPUT(NB,DAP_CODE)=
	BEGIN
	LOCAL TASK_BLOCK: VECTOR[3];
	TASK_BLOCK[0]=_TKFOT;
	TASK_BLOCK[2]=DAP_CODE;
	TSK(NB,3,TASK_BLOCK);
	END %;

MACRO DEVCHR(REG)=MACHOP(%O'47',REG,4) %;


MACRO	IOERRCODE(X)=
			BEGIN
			IF (.X XOR ENDFILE) EQL 0
				THEN WARNING(ENDFILE,.FILBLK)
				ELSE ERROR(IOERROR,.FILBLK,.X)
			END%;
MACRO ERR_INTERPRET=
	BEGIN
	%IF FTSIG
		%THEN
		IF .FF EQL 0
		THEN	BEGIN
			LOCAL IB: REF INT_BLOCK;
			%IF NOT FTPSI %THEN
			EXTERNAL ROUTINE
				UDT,
				TSIGNL,
				TSICAN;
			LOCAL RETRYIT: INT_BLOCK;
			LOCAL RTR;	!Handle for timer
			CLEARV(RETRYIT);!Empty block for wakeup only
			RTR=TSIGNL(RETRYIT,UDT()+.IORETINTERVAL);
			%FI
			IF (IB=.FILBLK[FILE$IB]) EQL 0
				THEN CRASH('Interrupt block missing');
			WAIT(.IB);	!Wait for 'done' interrupt
			%IF NOT FTPSI %THEN TSICAN(.RTR); %FI
			END
		ELSE IOERRCODE(FF)
	%ELSE IOERRCODE(FF)
	%FI
	END%;

MACRO	NPD=BLOCK[NPDLEN] FIELD(NPD_FIELDS) %;

!
! Field Definitions
!
FIELD NPD_FIELDS=
	SET
	NPD$NODE=[0,0,36,0],	!Node # (not name!) or -1 for any
	NPD$PIDLEN=[1,0,36,0],	!# of chars in PID
	NPD$PID=[2,0,0,0]	!PID starts here
	TES;
!
! Global Data
!
GLOBAL
	RNAME:	INITIAL(%SIXBIT'FTSFAL'),
	INNETB: INITIAL(DEFAULT_INNETB),	!# of input TSK buffers
	ONNETB:	INITIAL(DEFAULT_ONNETB),	!# of output TSK buffers
	DCONTO: INITIAL(DEFAULT_CONTO),
	DREPTO: INITIAL(DEFAULT_REPTO),
	MAXDRQ;				!Max for INNETB (Set at startup)

!
! Externals
!
EXTERNAL ROUTINE
FILOP,	!File Open/Close...
INTINI,	!Initialize an interrupt block
UDX,	!Get universal device index
WRSIXA,	!convert SIXBIT to ASCII
WRPPNA,	!convert PPN to ASCII
MOVEAZ,	!Move asciz string
CHKCON,	!Check for connect confirmed
ALLOC,	!Get some core
LKNODE,	!Find a node in NODTBL
ZERO;	!Clear a block of core

%IF NOT FTPSI %THEN
EXTERNAL ROUTINE
UDT,	!Universal date & time
TSIGNL,	!Set alarm clock
TSICAN;	!Turn off alarm clock
%FI

EXTERNAL RUN: REF PROCESS_BLOCK;
EXTERNAL IORETINTERVAL;	!How often to check for I/O completion

BUILTIN MACHSKIP;

!
! Macros
!


!
! Routines
!
GLOBAL ROUTINE NDBINI(FILBLK)=
!This sets up a file block for the first time it gets used
!It only sets up pointers, not filespecs.

BEGIN
EXTERNAL ROUTINE
FFIOCH;
MAP FILBLK: REF NDB;


!First Zero out everything

ZERO(.FILBLK,.FILBLK+NDB_LEN-1);

!Now fill in the required pointers in the FILOP. block

FILBLK[FILE$COUNT]=%FIELDEXPAND(FILE$ALLOC,0)-%FIELDEXPAND(FILE$COUNT,0);
					!# OF WORDS IN LOOKUP/ENTER BLOCK
FILBLK[FILE$LOOKUP]=FILBLK[FILE$COUNT];	!Set up pointer to lookup block
FILBLK[FILE$PATH_LEN]=SFDMAX+3;
FILBLK[FILE$PATH_BLOCK]=FILBLK[FILE$PATH_FUN];
FILBLK[FILE$I_BRH]=FILBLK[FILE$I_CBUFF];	!Input buffer ring header pointer
FILBLK[FILE$O_BRH]=FILBLK[FILE$O_CBUFF];	!Output ...
FILBLK[FILE$I_NBUFF]=.INNETB;			!Set # of buffers
FILBLK[FILE$O_NBUFF]=.ONNETB;			! "    "
FILBLK[FILE$DEVICE]=%SIXBIT 'TSK   ';
FILBLK[FILE$NAME]=.RNAME;			!Task name
%IF %VARIANT AND 8
%THEN
FILBLK[FILE$MODE]=UU_NBE+UU_AIO+_IOIMG+UU_DMR;
	!Non-blocking,non-blocking enter, byte, disable message reassembly
%ELSE
FILBLK[FILE$MODE]=UU_NBE+UU_AIO+_IOBYT+UU_DMR;
	!Non-blocking,non-blocking enter, byte, disable message reassembly
%FI
FILBLK[FILE$CHANNEL]=FFIOCH(.FILBLK);		!Get a free I/O channel
FILBLK[FILE$PF_NODE_A]=1;	!So everyone knows it's an NDB

!Set default timeout values (unless debugging)
	IF (.DREPTO NEQ 0) THEN FILBLK[NDB$REPTO]=.DREPTO;
	IF (.DCONTO NEQ 0) THEN FILBLK[NDB$CONTO]=.DCONTO;

RUN[P$NDB]=.FILBLK;		!This is the NDB for this process
END;

GLOBAL ROUTINE LINK(NB)=
!Open a logical link
!The link will be open, with interrupts set up, when the routine returns
!Argument:
!NB:	Node data block
!	This is a local task if NDB$NODEID is 0, otherwise it is remote
BEGIN
MAP	NB: REF NDB;
LOCAL	LOCNPD: NPD,		!Our NPD (7.00)
	REMNPD:	NPD;		!Other system's NPD (7.00)

IF .NB[NDB$NODEID] NEQ 0 THEN
	BEGIN	!This is a remote task
	LOCAL NODENAME;	!Name of node to connect to
	LOCAL NNM;	!Node number
	LOCAL NODE: REF NODTBL_ENTRY;	!The entry in NODTBL
	NODENAME=.NB[NDB$NODEID];	!Use the destination by default
	NODE=LKNODE(.NODENAME);	!Try to look up in NODTBL

	IF .NODE[NOD$CNAME] NEQ 0
	THEN NB[FILE$NAME]=.NODE[NOD$CNAME]; !Use taskname if any

	NB[FILE$LPPN]=(IF .NODE[NOD$CPPN] NEQ 0
	THEN	.NODE[NOD$CPPN]		!Use ppn given if any
	ELSE	XWD(1,2));

	IF .NODE[NOD$ROUTE] NEQ 0
	THEN NODENAME=.NODE[NOD$ROUTE];	!Use NOD$ROUTE if any

	NNM=NODENN(.NODENAME);	!Get the node number for the node
	IF .NNM EQL 0 THEN ERROR(FILUNN,.NB);	!He ain't there

	IF700			!Use new stuff for 7.00 or later
	THEN	BEGIN
		LOCAL PT;

		LOCNPD[NPD$NODE]=0;		!Ignored anyway
		LOCNPD[NPD$PIDLEN]=LOCAL_PID_LEN;
		MOVEAZ(%REF(CH$PTR(LOCAL_PID)),%REF(CH$PTR(LOCNPD[NPD$PID])));
		REMNPD[NPD$NODE]=.NNM; 		!Store the node number
		PT=CH$PTR(REMNPD[NPD$PID]);	!Start the name here
		WRSIXA(.NB[FILE$NAME],PT);	!Store the name
		WRPPNA(.NB[FILE$LPPN],PT);	!And the PPN
		REMNPD[NPD$PIDLEN]=
		 CH$DIFF(.PT,CH$PTR(REMNPD[NPD$PID])); !# Of characters in PID
		END

	ELSE	BEGIN		!This is for 6.03/6.03A
		(NB[FILE$DEVICE])<6,6>=(.NNM AND 7)+%O'20';
		(NB[FILE$DEVICE])<12,6>=((.NNM ^ -3) AND 7)+%O'20';
		!Stuff the node number into the device name
		END;

	NB[NDB$MASTER]=1;	!We are the active task
	END
ELSE	BEGIN
	NB[NDB$MASTER]=0;	!We are a passive task
	END;


!Rejoin common code
IF .NB[FILE$IB] EQL 0
THEN	INTINI(NB[FILE$IB]=ALLOC(INT_LEN));
	!Create an interrupt block if we don't have one

!Open the link & try to connect
	BEGIN
	BIND IB=.NB[FILE$IB]: INT_BLOCK;
	REGISTER R;

	DO	BEGIN
		LOCAL T;		!Handle for timer if we set it
		LOCAL WFLINK: INT_BLOCK; !To wake up periodically & check
		CLEARV(WFLINK);		!for an available link
		WFLINK[INT$PROCESS]=.RUN; !Wake us, that's all
		R=.NB[FILE$DEVICE];	!See if there are any links to there
		DEVCHR(R);
		IF ((.R AND DEV_OPENED) NEQ 0) OR ((.R AND DEV_AVAIL) EQL 0)
		THEN	BEGIN
			IF .NB[NDB$MASTER]
			THEN ERROR(LNKNAV,NB[FILE$START]) !none, try later
			ELSE	BEGIN	!Wait for one
				T=TSIGNL(WFLINK,UDT()+.DCONTO);
				WAIT(WFLINK);
				TSICAN(.T);
				END
			END
		ELSE	EXITLOOP;
		END	WHILE 1;

	IB[INT$PROCESS]=.RUN;		!These interrupts belong to us
	RUN[P$NDB]=.NB;			!This is our NDB

	OPEN_R(.NB);

	IB[INT$WHAT]=.NB[FILE$CHANNEL];
	WAKESET(IB,IN_DONE,OUT_DONE,ONLINE);
	ERRSET(IB,IN_ERROR,OUT_ERROR,OFFLINE);
	IB[INT$SIGNAL_ARGS]=4;	!Set up arguments for interrupts
	IB[INT$SEVERITY]=SS$_ERROR;
	IB[INT$STSCODE]=IOINT;
	IB[INT$FILBLK]=.NB;

	INTERRUPTS(ADD,IB);

		BEGIN
		%IF NOT FTPSI %THEN
		LOCAL	LINKRETRY: INT_BLOCK,
			LR;	!Handle for timer
		CLEARV(LINKRETRY);
		%FI

		IF .NB[NDB$MASTER] NEQ 0
		!Do ENTER, wait for interrupt, then check for good connection
		THEN	BEGIN	!Active task, ENTER first

			IF700
			THEN	BEGIN
				LOCAL TSKBLK: VECTOR[4];
				TSKBLK[0]=_TKFEI;	!Must be idle first
				TSK(NB[FILE$START],2,TSKBLK); !...

				TSKBLK[0]=_TKFEA;	!Enter active state
				TSKBLK[2]=LOCNPD;	!Local NPD
				TSKBLK[3]=REMNPD;	!Remote NPD
				(TSKBLK[2])<LH>=(TSKBLK[3])<LH>=NPDLEN;
				 !Length of NPD's
				TSK(NB[FILE$START],4,TSKBLK);		!Do it
				END		!7.00 and later

			ELSE	BEGIN		!6.03/6.03A
				ENTER(.NB);
				END;		!6.03/6.03A

			WHILE CHKCON(NB[FILE$START]) EQL 0
			DO	BEGIN
				%IF NOT FTPSI %THEN !Set timer
				LR=TSIGNL(LINKRETRY,UDT()+.IORETINTERVAL);
				%FI
				WAIT(IB);
				%IF NOT FTPSI %THEN TSICAN(.LR); %FI
				END
			END

		ELSE	BEGIN	!Passive Task, wait for interrupt then ENTER
			DO	BEGIN
				%IF NOT FTPSI %THEN	!Set timer
				LR=TSIGNL(LINKRETRY,UDT()+.IORETINTERVAL);
				%FI
				WAIT(IB);
				%IF NOT FTPSI %THEN TSICAN(.LR); %FI
				END WHILE CHKCON(NB[FILE$START]) EQL 0;
				!Make sure we really got a connect
			IF700 THEN () ELSE ENTER(.NB);
			END;
		END;
	END;
END;	!LINK
GLOBAL ROUTINE NODENN(NN)=
!Routine to return a node number from a node name or visa-versa
BEGIN
REGISTER T;	!Register for UUO
LOCAL NODE_UUO_BLK: VECTOR[2];

T=NODE_UUO_BLK;		!Addr
T<18,18>=2;			!Function code
NODE_UUO_BLK[0]=2;		!Length of argument list (includes this word)
NODE_UUO_BLK[1]=.NN;		!Sixbit NODEID or node number
IF CALLI(T,%O'157')		!NODE. UUO
	THEN RETURN .T		!Return number or name
	ELSE RETURN 0;		!Node isn't there
END;	!NODENN
GLOBAL ROUTINE XINPUT(FILBLK)=
!ROUTINE TO INPUT A BUFFER.
!TAKES FILE BLOCK AS ARGUMENT
!RETURN WIN

BEGIN
MAP FILBLK: REF NDB;
LOCAL	TTO,
	INTMO: INT_BLOCK;

EXTERNAL ROUTINE
	INPUT;

ROUTINE XINHANDLE(SIGNAL_ARGS,MECH_ARGS,ENABLE_ARGS)=
	BEGIN
	MAP SIGNAL_ARGS: REF VECTOR,
	    MECH_ARGS: REF VECTOR,
	    ENABLE_ARGS: REF VECTOR;
	BIND NB=.ENABLE_ARGS[1]: NDB;
	SELECT .$CODE OF SET
	[TIMOUT]:	BEGIN
			IF .NB[FILE$I_COUNT] GEQ 0
				THEN	BEGIN	!Buffer has been sent
					EXTERNAL RUN: REF PROCESS_BLOCK;
					RUN[P$WAIT]=0;
					RETURN SS$_CONTINUE;
					END;
			END;
	[ALWAYS]:	RETURN SS$_RESIGNAL;
	TES
	END;

ESTABLISH (XINHANDLE,.FILBLK);

IF (TTO=.FILBLK[NDB$REPTO]) NEQ 0 THEN
	BEGIN
	CLEARV(INTMO);
	INTMO[INT$SIGNAL_ARGS]=2;	!Length of argument list
	INTMO[INT$STSCODE]=TIMOUT;
	INTMO[INT$SEVERITY]=SS$_ERROR;
	INTMO[INT$STATUS]=INERROR;	!Remember it was input
	TTO=TSIGNL(INTMO,(UDT()+.TTO));
	END
ELSE	TTO=-1;	!No limit

IF700
THEN	BEGIN
	LOCAL NOEOR;
	INOEOR(FILBLK)=0;	!Clear old style bit
	NOEOR=TSK_INPUT(FILBLK[FILE$START]);
	!Do TSK. UUO with appropriate function code
	INOEOR(FILBLK)=
	(IF .NOEOR EQL DC_DAT THEN 1 ELSE 0);	!Set the bit from the NCL DAP
	END
ELSE	INPUT(FILBLK[FILE$START]);

IF .TTO NEQ -1
THEN	BEGIN
	TSICAN(.TTO);	!Cancel timeout request if any
	END;

IF NOT .FILBLK[NDB$MFLAG_LEN]
 THEN FILBLK[NDB$MLENGTH]=.FILBLK[NDB$NSPMLENGTH]+.INOEOR(FILBLK);
 !If we don't know how long the message is supposed to be,
 !it must be the whole packet, at least.  If no end of record, it
 !must be at least 1 character longer than that.

%IF FTTRACE %THEN
IFMSG(TRACE,(EXTERNAL ROUTINE TIBUF; TIBUF(FILBLK[FILE$START])));
%FI;	!Type the message, if requested

WIN

END;


GLOBAL ROUTINE XOUTPUT(FILBLK)=
!ROUTINE TO OUTPUT A BUFFER.
!TAKES FILE BLOCK AS ARGUMENT
!RETURN WIN

BEGIN
MAP FILBLK: REF NDB;
LOCAL	T,		!Store value from OUTPUT
	OUTTMO: INT_BLOCK,	!Interrupt block for timeout request
	TTO;		!When to time out

EXTERNAL ROUTINE
	TSIGNL,
	UDT,
	TSICAN,
	OUTPUT;

ROUTINE XOUTHANDLE(SIGNAL_ARGS,MECH_ARGS,ENABLE_ARGS)=
	BEGIN
	MAP SIGNAL_ARGS: REF VECTOR,
	    MECH_ARGS: REF VECTOR,
	    ENABLE_ARGS: REF VECTOR;
	BIND NB=.ENABLE_ARGS[1]: NDB;
	SELECT .$CODE OF SET
	[TIMOUT]:	BEGIN
			IF ..NB[FILE$O_CBUFF] GEQ 0
				THEN	BEGIN	!Buffer has been sent
					EXTERNAL RUN: REF PROCESS_BLOCK;
					RUN[P$WAIT]=0;
					RETURN SS$_CONTINUE;
					END;
			END;
	[ALWAYS]:	RETURN SS$_RESIGNAL;
	TES
	END;

ESTABLISH (XOUTHANDLE,.FILBLK);

%IF FTTRACE %THEN
IFMSG(TRACE,(EXTERNAL ROUTINE TOBUF; TOBUF(FILBLK[FILE$START])));
%FI;	!Type the message, if requested

IF (TTO=.FILBLK[NDB$REPTO]) NEQ 0 THEN
	BEGIN
	CLEARV(OUTTMO);
	OUTTMO[INT$SIGNAL_ARGS]=2;	!length of argument list
	OUTTMO[INT$STSCODE]=TIMOUT;
	OUTTMO[INT$SEVERITY]=SS$_ERROR;	!Bomb out transfer if not caught
	OUTTMO[INT$STATUS]=OUTERROR;
	TTO=TSIGNL(OUTTMO,(UDT()+.TTO));
	END
ELSE	TTO=-1;

IF700
THEN	BEGIN
	LOCAL NOEOR;
	NOEOR=.ONOEOR(FILBLK);
	ONOEOR(FILBLK)=0;	!Clear old style bit
	T=TSK_OUTPUT(FILBLK[FILE$START],(IF .NOEOR THEN DC_DAT ELSE DC_DAR));
	  !Do TSK. UUO with appropriate function code
	END
ELSE	T=OUTPUT(FILBLK[FILE$START]);


%IF %DECLARED(NDB$MAXNSP)
	%THEN
	IF .FILBLK[NDB$MAXNSP] NEQ 0
		THEN FILBLK[FILE$O_COUNT]=.FILBLK[NDB$MAXNSP];
	%FI
IF .TTO NEQ -1
THEN	BEGIN
	TSICAN(.TTO);	!Cancel timeout request
	END;
.T
END;

GLOBAL ROUTINE XIN_NOWAIT(FILBLK)=
!Same as XINPUT but does not ever call WAIT
BEGIN
MAP FILBLK: REF NDB;
BIND NOWAIT=.FILBLK[FILE$NOWAIT];	!Save state of bit
LOCAL V;

FILBLK[FILE$NOWAIT]=1;
V=XINPUT(.FILBLK);
FILBLK[FILE$NOWAIT]=NOWAIT;
.V
END; !XIN_NOWAIT
GLOBAL ROUTINE XOUT_NOWAIT(FILBLK)=
!Same as XOUTPUT but does not ever call WAIT
BEGIN
MAP FILBLK: REF NDB;
BIND NOWAIT=.FILBLK[FILE$NOWAIT];
LOCAL V;

FILBLK[FILE$NOWAIT]=1;
V=XOUTPUT(.FILBLK);
FILBLK[FILE$NOWAIT]=NOWAIT;
.V
END; !XOUT_NOWAIT

GLOBAL ROUTINE XREAD(FILBLK,DEST,MAXCHARS,TERMINATOR)=
!ROUTINE TO READ A STRING FROM A FILE
!PARAMETERS:
!FILBLK: FILE BLOCK (SOURCE)
!DEST:	ADDR OF DESTINATION BYTE POINTER
!MAXCHARS: MAXIMUM # OF CHARACTERS TO INPUT;
!TERMINATOR: TERMINATE INPUT IF THIS CHARACTER IS FOUND
!RETURNS WIN if count exhausted, TERMINATOR otherwise
BEGIN
LOCAL C;
	DECR MAXC FROM .MAXCHARS TO 1 DO BEGIN
	C=XBINA(.FILBLK,.DEST);
	IF CH$RCHAR(..DEST) EQL .TERMINATOR THEN RETURN .TERMINATOR;
	END;
RETURN WIN;
END;
GLOBAL ROUTINE XWRITE(FILBLK,ADDR,MAXCHARS,TERMINATOR)=
!ROUTINE TO WRITE A STRING TO A FILE
!Parameters:
!FILBLK: FILE BLOCK (Destination)
!ADDR: ADDR OF SOURCE BYTE POINTER
!MAXCHARS: MAXIMUM # OF CHARACTERS TO OUTPUT;
!TERMINATOR: TERMINATE OUTPUT IF THIS CHARACTER IS FOUND
!Returns WIN if count exhausted, TERMINATOR otherwise
BEGIN
LOCAL C;
	DECR MAXC FROM .MAXCHARS TO 1 DO BEGIN
	C=XBOUTA(.FILBLK,.ADDR);
	IF CH$RCHAR(..ADDR) EQL .TERMINATOR THEN RETURN .TERMINATOR;
	END;

END;
GLOBAL ROUTINE XBINA(FILBLK,DEST)=
!READ ONE BYTE FROM A FILE and increment destination byte pointer
!FILBLK: FILE BLOCK
!DEST:  Addr of DESTINATION BYTE POINTER
!RETURNS WIN
BEGIN
LOCAL R;
MAP FILBLK: REF NDB;
UNTIL (FILBLK[NDB$NSPMLENGTH]=.FILBLK[NDB$NSPMLENGTH]-1) GEQ 0 DO
	BEGIN
	IF .INOEOR(FILBLK) EQL 0 THEN ERROR(DAPEOM);
	R=XINPUT(.FILBLK);
	END;
CH$WCHAR_A(CH$RCHAR_A(FILBLK[FILE$I_PTR]),.DEST);
FILBLK[NDB$MLENGTH]=.FILBLK[NDB$MLENGTH]-1; !Decr the DAP length too
WIN	!SUCCESSFUL RETURN VALUE
END;
GLOBAL ROUTINE XBIN(FILBLK)=
!READ ONE BYTE FROM A FILE
!FILBLK: FILE BLOCK
!RETURNS BYTE READ
BEGIN
LOCAL R;
MAP FILBLK: REF NDB;
UNTIL (FILBLK[NDB$NSPMLENGTH]=.FILBLK[NDB$NSPMLENGTH]-1) GEQ 0 DO
	BEGIN
	IF .INOEOR(FILBLK) EQL 0 THEN ERROR(DAPEOM);
	R=XINPUT(.FILBLK);
	END;
FILBLK[NDB$MLENGTH]=.FILBLK[NDB$MLENGTH]-1; !Decr the DAP length too
CH$RCHAR_A(FILBLK[FILE$I_PTR])
END;
GLOBAL ROUTINE XBOUTA(FILBLK,SOURCE)=
!WRITE ONE BYTE TO A FILE and increment source pointer
!FILBLK: FILE BLOCK
!SOURCE:  Addr of SOURCE BYTE POINTER
!Returns WIN
BEGIN
MAP FILBLK: REF  NDB;
IF (FILBLK[FILE$O_COUNT]=.FILBLK[FILE$O_COUNT]-1) LSS 0 THEN
	BEGIN
	ONOEOR(FILBLK)=1;	!Indicate not yet end of message
	XOUTPUT(.FILBLK);
	END;
CH$WCHAR_A(CH$RCHAR_A(.SOURCE),FILBLK[FILE$O_PTR]);
WIN	!SUCCESSFUL RETURN VALUE
END;
GLOBAL ROUTINE XBOUT(FILBLK,SOURCE)=
!WRITE ONE BYTE TO A FILE
!FILBLK: FILE BLOCK
!SOURCE: Byte to write
!Returns WIN
BEGIN
MAP FILBLK: REF  NDB;
UNTIL (FILBLK[FILE$O_COUNT]=.FILBLK[FILE$O_COUNT]-1) GEQ 0 DO
	BEGIN
	ONOEOR(FILBLK)=1;	!Indicate not yet end of message
	XOUTPUT(.FILBLK);
	END;
CH$WCHAR_A(.SOURCE,FILBLK[FILE$O_PTR]);
WIN	!SUCCESSFUL RETURN VALUE
END;
GLOBAL ROUTINE XMTMSG(NB,ADDR,LEN)=
!Routine to send a message
!Arguments:
!NB:	NDB for node
!ADDR:	address of message
!LEN:	length of message
BEGIN
MAP NB: REF NDB;
LOCAL PTR;	!Temp byte pointer

PTR=CH$PTR(.ADDR,0,8);	!Make pointer point to message
DECR L FROM .LEN TO 1 DO XBOUTA(.NB,PTR);
END;
GLOBAL ROUTINE ENTER(NB)=
!Routine to do an 'ENTER' to a file block
!FILOP cannot do this correctly for a TSK
!ARGUMENT
!NB:	NDB
BEGIN
MAP NB: REF NDB;
REGISTER FF;	!AC To build instruction in
MAP FF: INSTRUCTION;

FF=0;
FF[INSTR$OPCODE]=%O'77';	!Opcode for ENTER UUO
FF[INSTR$AC]=.NB[FILE$CHANNEL];	!Channel # in AC field
FF[INSTR$ADDRESS]=.NB[FILE$LOOKUP];	!Address is addr of lookup block
IF MACHSKIP(%O'256',0,FF)	!XCT an ENTER UUO
	THEN RETURN WIN	!It worked!
	ELSE ERROR(FILERR+.NB[FILE$ADATE])
		!Of all the wierd places to return an error code!!
END;	!ENTER
GLOBAL ROUTINE TSK(FILBLK,LEN,TSKBLK)=
!Do A TSK. UUO for 7.00 or later
! FILBLK= NDB (task file block) for link
! LEN= Length of argument block
! TSKBLK= Address of argument block
! The NCL DAP code from an input function (_TKFIN) will be returned

!
! Abstract: BLISS-36 interface to the TOPS-10 7.00 TSK. UUO.
!
%(	TSK. UUO calling sequence:

	MOVE	AC,[LEN,,BLK]
	TSK.	AC,		;TSK. = CALLI 177
	 (ERROR return)
	(WIN return)

.
.
.
BLK[0]=	Function code: One of the following
	1: Read status
	2: Enter Passive state
	3: Enter Active State
	4: Enter Idle State
	5: Wait for Connect Confirm
	6: Output (Send) 1 packet
	7: Input (receive) 1 packet

BLK[1]=	Channel #

!Rest of argument block depends on function selected

Function:	1	2,3	4,5	6	7

BLK[2]=	   STATUS*   Local NPD   .   MESTYPE* MESTYPE
BLK[3]=	  Local NPD* Remote NPD	 .	.	.
BLK[4]=	 Remote NPD*	 .	 .	.	.

 * indicates returned value

NPD[0]= Node number or -1 for any
NPD[1]= length of task name (in words)
NPD[2...]= Task name (ASCII)

STATUS=
	0:	Idle
	1:	Passive (Waiting for Connect Initiate)
	2:	Waiting for Connect Confirm
	3:	Active
	4:	Waiting for Disconnect Confirm

)%


!
! Formal Parameters
!

BEGIN
MAP	FILBLK: REF NDB;	!TSK file block
MAP	TSKBLK: REF VECTOR;	!Argument block for TSK. UUO
LOCAL	SAVE_DAP_CODE;		!UUO clobbers it if it fails
REGISTER
	FF;			!These UUOs like a register too.

TSKBLK[1]=.FILBLK[FILE$CHANNEL]; !Get channel #
SAVE_DAP_CODE=.TSKBLK[2];	!Save this away...

DO	BEGIN
	TSKBLK[2]=.SAVE_DAP_CODE;
	FF<LH>=.LEN;			!Length of arg blk
	FF<RH>=TSKBLK[0];			!Addr

	IF CALLI(FF,TSK_)
	THEN	RETURN .TSKBLK[2]		!Success, return DAP code
	ELSE	BEGIN
		IF .FF EQL TS_UDW	!I/O-type error, or not finished yet
		THEN	FF=.TSKBLK[2] AND %O'760000'	!Get the status bits
		ELSE	FF=%O'777777770000'+.FF; !UUO error of some sort
		ERR_INTERPRET;	!Interpret the error or wait for completion
		END
	END WHILE 1;
END;
END	ELUDOM