	.TITLE	PYDRIVER - Pseudo terminal driver interface
	.IDENT 'V05-011A'

;++
; FACILITY:
;
;	VAX/VMS Pseudo Terminal Driver interface
;
; ABSTRACT:
;
;	The pseudo terminal consists of two devices.
;	This is the non terminal part of the two devices.
;
; AUTHOR:
;
;	19-Nov-1982	Dale Moore	Redid the TW driver for VMS 3.0
;
;	This program has been granted to the public domain by the author.
;
; Revision History:
;
;	Version 'V03-001'
;		DWM	- Added Page seperators
;			- On Last cancel, invoke hangup on TW device
;			- changed PY_STOP and PY_STOP2 to return instead
;			  of looping for more.
;			- Changed last cancel to call ioc$reqcom instead of
;			  using macro REQCOM which is a branch ioc$reqcom.
;	Version V03-002	- Changed to Clear word rather than clear byte
;			  in startio routine on word field.
;
;	Version V03-003 (Thu Dec  9 12:42:38 1982) D. Kashtan
;			  Made into a TEMPLATE driver.
;	Version V03-004 (Fri Dec 10 11:40:35 1982) D. Kashtan
;			  Made EXE$... into +EXE$... in FDT dispatch table,
;			  fixing bug that crashed system in SET/SENSE MODE/CHAR
;	Version V03-005	(14-Jun-1983) Dale Moore
;			  Add R4 to calls to IOC$INITIATE.
;			  TTY$STARTIO mucks R4
;	Version V03-006	(12-Jul-1983) Mark London, MIT Plasma Fusion Center
;			- Set terminal to NOBROADCAST when no READ QIO avail-
;			  able so as to allow Broadcasts without hanging up.
;			  (When no QIO available, UCB$M_INT is enabled, and
;			  the Broadcast don't get handled.  The sender of a
;			  Broadcast goes into a wait state until the broadcast
;			  is completed or timed-out, neither or which can
;			  happen.  Setting NOBROADCASTs at least allow the
;			  Broadcast to finish. What is needed is a CTRLS state
;			  that doesn't allow Broadcasts to break through.)
;			- Added MOVC3 instruction for burst data in PY$STARTIO,
;			  which "should" speed up the transfers.
;			- Fixed data transfer problem by raising to fork IPL
;			  while calling PUTNXT in PY$FDTWRITE. NOTE: TWA0 must
;			  be a mailbox to avoid TT reads from timing out.
;
;	Version V04-001 - Doug Davis, Digital Equipment
;			- Most of the changes required for migration to
;			  Version 4.0 relate to the new handling of UCB
;			  creation and deletion. This includes adding
;			  a CLONEDUCB entry point to the dispatch table,
;			  and "cloning" the UNITINIT routine to handle the
;			  required entry. Also changed the call from
;			  IOC$CREATE_UCB to IOC$CLONE_UCB, with associated
;			  maintainence of the UCB$V_DELETEUCB bit in the
;			  UCB$L_STS field.
;			- Changes were also incorporated reflecting new
;			  methods of  XON/XOFF flow control.
;			- Although pieces of the original code have been
;			  superceded by these changes ( example - functions
;			  that were performed by Unit_Init for new units
;			  are are now performed by Clone_Init ), most of
;			  the original code was left in place and/or commented
;			  out.
;
;			NOTE - No subroutines preambles were modified to
;			       reflect these changes.
;
;	Version V04-002	(20-Jan-1985) Mark London, MIT Plasma Fusion Center
;			- Changed test for output characters after call to
;			  UCB$L_TT_PUTNXT and UCB$L_TT_GETNXT.  Output is
;			  indicated in UCB$B_TT_OUTYPE.
;
;	Version V04-003 (24-Jun-1985) Kevin Carosso, Hughes Aircraft Co., S&CG
;			Cleaned this thing up quite a bit.
;			- Got rid of MBX characteristic on the devices.  This
;			  was a holdover to before cloned devices really
;			  existed.
;			- Got rid of the UNIT_INIT routine completely.  This
;			  was replaced by a CLONE_UCB routine.
;			- Leave the PY template device OFFLINE.  This is what
;			  other TEMPLATE devices do, to indicate that you
;			  really cannot do I/O to the template.
;			- Rewrote the CANCEL_IO routine to issue a DISCONNECT
;			  on the TW device at last deassign of the PY device.
;			  This causes the TW device to hangup on it's process.
;			  Works quite nicely with VMS V4 connect/disconnect
;			  mechanism.  Also, the devices should never stay
;			  around after last deassign on the PY, if you want to
;			  reconnect, count on VMS connect/disconnect instead,
;			  it's much less of a security hole.
;			- Got rid of all modem operations.  Improper use tended
;			  to crash the system and they are not necessary.  TW
;			  device is always NOMODEM.  HANGUP works as you want
;			  it to without the modem stuff.
;			- Got rid of the BRDCST on/off stuff.  It doesn't seem
;			  to be necessary any more.  It also had a bug in it
;			  somewhere that caused the terminal to start off
;			  NOBRDCST when it shouldn't.
;			- General house-cleaning.  Got rid of commented out
;			  lines from VMS V3 version.  Fixed up typos in
;			  comments.
;
;	Version V04-004 (10-Feb-1986) Kevin Carosso, Hughes Aircraft Co., S&CG
;			Changed all references to PTDRIVER to TPDRIVER because
;			DEC (bless their little hearts) invented the *#&#$&
;			TU81 and use PTA0: now.
;
;	Version V04-005 (3-Sep-1986) Kevin Carosso, Hughes Aircraft Co., S&CG
;			Fixed bug whereby the sequence ^S followed by ^Y
;			would cause a system hang.  The fix is really in
;			TWDRIVER.
;
;	Version V04-006 (5-Dec-1986) Kevin Carosso, Hughes Aircraft Co., S&CG
;			Fixed the infamous character munging bug.  Turns
;			out that in FDTWRITE we were enabling interrupts
;			after doing the PUTNXT and before checking for
;			a char.  Now check for the character and then
;			ENBINT after we've decided what to do.  I assume
;			UCB$B_TT_OUTYPE field was getting corrupted.
;
;	Version V04-007 (10-JUL-1987) Kevin Carosso, Hughes Aircraft Co., S&CG
;			Fix in TWDRIVER for timeouts.  Don't bother
;			clearing the TIM bit all the time in here now.
;
;			Also, while we're in here, lets make the device
;			acquire "NODE$" prefixes, since mailboxes do.
;
;	Version V04-008	(2-NOV-1987) Kevin Carosso, Network Research Co.
;			In PY$FDTWRITE we were overwriting the status in
;			R0 just before jumping to EXE$ABORTIO when something
;			goes wrong.  Don't POPR into R0, but into R1.  We
;			only care about what we POPR into R3 anyway.
;
;			Fix thanks to Gerard K Newman @ San Diego
;			Supercomputer Center.
;
;	Version V05-001 Digital Equipment Corp.
;			Add support for Symmetric Multiprocessing.
;
;	Version	V05-002	Digital Equipment Corp.
;			Modify PY$CLONE_INIT to make newly cloned
;			owned by person requesting new device.
;
;	Version	V05-003	Digital Equipment Corp.
;			Modify PY$CLONE_INIT to change device protection
;			to be S:RWLP,O:RWLP.
;
;	Version	V05-004	Digital Equipment Corp
;			Change TP driver names to TW. (TP driver it turns
;			out, conflicted with the VAX/PSI terminal driver.)
;			TWDRIVER and PYDRIVER are now registered with SQM
;			so there will be no more conflicts.
;
;	Version	V05-005 Digital Equipment Corp
;			If input stopped then exit PY$FDTWRITE immediately
;			with reason of SS$_DATAOVERUN.  If we detect input
;			stopping while inserting data return with
;			SS$_DATAOVERUN and number of bytes inserted.
;
;	Version V05-006	Digital Equipment Corp.
;			Add CODE for sending an AST if PORT XON, PORT XOFF,
;			and PORT SET_LINE routines are called.  Also add
;			SENSEMODE, and SENSECHAR routine for reading TW device
;			characteristics.
;
;	Version V05-006A Kevin Carosso @ Network Research Co.
;			Conditionalize assembly for VMS V4 or V5.  There
;			is a magic symbol at the top that is commented
;			out for VMS V5.
;
;	Version V05-007A Digital Equipment Corp.
;			In PY$FDTWRITE the second test to see if device
;			is XOFFed is using the WRONG UCB.  Changed to use
;			correct UCB.
;
;	Version V05-008A Kevin Carosso @ Network Research Co.
;			Add a missing # on an immediate which was causing
;			CTRL/S to hang the driver.
;
;	Version V05-009A Kevin Carosso @ Network Research Co.
;			Modify PY$FDTWRITE to allow writes larger than
;			64K by using longword rather than word instructions.
;			Make the conditional assembly for VMS V5 automatic
;			driven off existence of SS$_INVLICENSE.
;
;	Version V05-010A Digital Equipment Corp.
;			Add code to allow PY$FDTWRITE to return echoed
;			data as part of the write operations.  This should
;			represent about a 2X savings in CPU cost for
;			echoed input.
;
;	Version V05-011A Digital Equipment Corp.
;			Add missing index to fix (32*N)+1 bug.
;
;--

	.PAGE
	.SBTTL	Declarations

	.LIBRARY	/SYS$LIBRARY:LIB.MLB/

;
; External Definitions:
;
.NOCROSS
	$CRBDEF				; Define CRB
	$CANDEF				; Define cancel codes
	$DDBDEF				; DEFINE DDB
	$DDTDEF				; DEFINE DDT
	$DEVDEF				; DEVICE CHARACTERISTICS
	$DYNDEF				; Dynamic structure definitions
	$IODEF				; I/O Function Codes
	$IRPDEF				; IRP definitions
	$JIBDEF				; Define JIB offsets
	$ORBDEF				; Define ORB offsets
	$PCBDEF				; Define PCB
	$PRDEF				; Define PR
	$SSDEF				; DEFINE System Status
;
; If SS$_INVLICENSE exists, we must be building on V5.  Go ahead and
; define our conditionals so we can build a V5 driver.
;
.IF	DEFINED	SS$_INVLICENSE
	VMS_V5 = 1
	.PRINT		; PYDRIVER -- Building VMS V5 compatible driver
.IF_FALSE
	.PRINT		; PYDRIVER -- Building VMS V4 compatible driver
.ENDC

.IF	DEFINED	VMS_V5
	$SPLCODDEF			; Spin lock code definitions
.ENDC

	$TTDEF				; DEFINE TERMINAL TYPES
	$TT2DEF				; Define Extended Characteristics
	$TTYDEF				; DEFINE TERMINAL DRIVER SYMBOLS
	$TTYDEFS			; DEFINE TERMINAL DRIVER SYMBOLS
	$TTYMACS			; DEFINE TERMINAL DRIVER MACROS
	$UCBDEF				; DEFINE UCB
	$VECDEF				; DEFINE VECTOR FOR CRB
.CROSS

;
; Local definitions
;
; QIO Argument list offsets
;
P1 = 0
P2 = 4
P3 = 8
P4 = 12
P5 = 16
P6 = 20
;
; New device class for control end
;
DC$_PY = ^XFF
DT$_PY = 0

;
; Definitions that follow the standard UCB fields for TW driver
;  This will all probably have to be the same as the standard term

	$DEFINI	UCB			; Start of UCB definitions

	.=UCB$K_TT_LENGTH		; Position at end of UCB

$DEF	UCB$L_TW_XUCB	.BLKL	1	; UCB of corresponding
					;  control/application unit
					; call
$DEF	UCB$L_TW_XON_AST .BLKL	1	; AST list for XON event notification
$DEF	UCB$L_TW_XOFF_AST .BLKL	1	; AST list for XOFF event notification
$DEF	UCB$L_TW_SET_AST .BLKL	1	; AST list for notification of SET_LINE
$DEF	UCB$K_TW_LEN			; Size of UCB

	$DEFEND	UCB			; End of UCB definitions

;
; Definitions that follow the standard UCB fields in PY devices
;

BUFFER_SIZE = 32
    ASSUME BUFFER_SIZE LE 512

	$DEFINI UCB			; Start of UCB definitions
 	.=UCB$K_LENGTH			; position at end of UCB
$DEF	UCB$L_PY_XUCB	.BLKL 1		; UCB of terminal part of pseudo terminal
$DEF	UCB$T_PY_BUFFER	.BLKB	BUFFER_SIZE ; Buffer to store characters to be transmitted
$DEF	UCB$K_PY_LEN			; Size of UCB

 	$DEFEND UCB			; end of UCB definitions

	.PAGE
;
; LOCAL Storage
;
	.PSECT $$$105_PROLOGUE

	.SBTTL	Standard Tables

;
; Driver prologue table:
;
PY$DPT::
	DPTAB	-			; Driver Prologue table
		END = PY$END,-		; End and offset to INIT's vectors
		UCBSIZE = UCB$K_PY_LEN,-; Size of UCB
		FLAGS=DPT$M_NOUNLOAD,-		; Don't allow unload
		ADAPTER=NULL,-			; ADAPTER TYPE
		NAME	= PYDRIVER		; Name of driver
	DPT_STORE INIT
	DPT_STORE UCB,UCB$W_UNIT_SEED,W,0	; SET UNIT # SEED TO ZERO

.IF	NOT_DEFINED	VMS_V5
	DPT_STORE UCB,UCB$B_FIPL,B,8 		; FORK IPL
.IF_FALSE
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8 ; FORK LOCK
.ENDC

	DPT_STORE UCB,UCB$W_STS,W,-		; TEMPLATE device
			<UCB$M_TEMPLATE>
	DPT_STORE UCB,UCB$L_DEVCHAR,L,<-	; Characteristics
			DEV$M_REC!-		;   record oriented
			DEV$M_AVL!-		;   available
			DEV$M_IDV!-		;   input device
			DEV$M_ODV>		;   output device
	DPT_STORE UCB,UCB$L_DEVCHAR2,L, -		; Device characteristics
			<DEV$M_NNM>			; prefix with "NODE$"
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_PY
	DPT_STORE UCB,UCB$B_DIPL,B,8		; Device IPL = FIPL (no device)
	DPT_STORE DDB,DDB$L_DDT,D,PY$DDT

	DPT_STORE REINIT
	DPT_STORE CRB,CRB$L_INTD+VEC$L_INITIAL,D,PY$INITIAL	; Controller
	DPT_STORE END

	.PAGE
	.SBTTL Driver Dispatch table and function decision table
;
; Driver Dispatch table
;
	DDTAB	DEVNAM	= PY,-			; Device name
		START	= PY$STARTIO,-		; Start I/O routine
		FUNCTB	= PY$FUNCTAB,-		; The function table
		CANCEL	= PY$CANCEL,-		; the cancel i/o routine
		CLONEDUCB = PY$CLONE_INIT	; Entry when template cloned.
;
; Function Decision table for PY devices
;
PY$FUNCTAB:
	FUNCTAB	,-			; Legal Functions
		<READLBLK,-
		WRITELBLK,-
		READVBLK,-
		WRITEVBLK,-
		READPBLK,-
		WRITEPBLK,-
		SETMODE,-
		SETCHAR,-
		SENSEMODE,-
		SENSECHAR,-
		>
	FUNCTAB	,-			; Buffered I/O functions
		<READLBLK,-
		WRITELBLK,-
		READVBLK,-
		WRITEVBLK,-
		READPBLK,-
		WRITEPBLK,-
		>
	FUNCTAB	PY$FDTREAD,<READLBLK,READVBLK,READPBLK>
	FUNCTAB PY$FDTWRITE,<WRITELBLK,WRITEVBLK,WRITEPBLK>
	FUNCTAB PY$FDTSET,<SETMODE,SETCHAR>
	FUNCTAB PY$FDTSENSEM,<SENSEMODE>
	FUNCTAB	PY$FDTSENSEC,<SENSECHAR>


	.SBTTL	Local Storage - Name of companion device

TWSTRING:	.ASCII	/TWA/
TWLENGTH = . - TWSTRING

	.PAGE
	.SBTTL	PY$FDTREAD - Function decision routine for PY control read
;++
; PY$FDTREAD
;
; Functional Description:
;
;	This routine is called from the function decision table dispatcher
; 	to process a read physical, read logical, read virtual I/O function.
;
;	The function first verifies the caller's parameters, terminating
;	the request with immediate success or error if necessary.
;	A system buffer is allocated and its
;	address is saved in the IRP.  The caller's quota is updated, and
;	the read request is queued to the driver for startup.
;
; Inputs:
;
;	R0,R1,R2	= Scratch
;	R3		= IRP Address
;	R4		= Address of PCB for current process
;	R5		= Device UCB address
;	R6		= Address of CCB
;	R7		= I/O function code
;	R8		= FDT Dispatch addr
;	R9,R10,R11	= Scratch
;	AP		= Address of function parameter list
;			P1(AP) = Buffer Address
;			P2(AP) = Buffer Size
;
; Outputs:
;
;	R0,R1,R2,R11	= Destroyed
;	R3-R10,AP	= Preserved (pickled)
;	IRP$L_SVAPTE(R3)= Address of allocated system buffer
;	IRP$W_BOFF(R3)	= Requested byte count
;
;	System Buffer:
;		LONGWORD/0	= Address of start of data= buff+12
;		LONGWORD/1	= Address of user buffer
;
;--
PY$FDTREAD::
	MOVZWL	P2(AP),R1	; Get buffer Size
	BNEQ	15$
	JMP	10$		; Is the size zero? If so, go do it easy.
15$:	MOVL	P1(AP),R0	; Get buffer Address
	JSB	G^EXE$READCHK	; Do we have access to the buffer
	PUSHR	#^M<R0,R3>	; Save user buffer address and IRP address
	ADDL	#12,R1		; Add 12 bytes for buffer header

.IF	NOT_DEFINED	VMS_V5
	JSB	G^EXE$BUFFRQUOTA	; Is there enough buffer space left in
					;  the quota?
	BLBC	R0, 30$			; Branch if insufficient quota
	JSB	G^EXE$ALLOCBUF		; Allocate a system buffer
.IF_FALSE
	JSB	G^EXE$DEBIT_BYTCNT_ALO 	; Verify enough byte quota allocate buffer
					; and charge process for useage
.ENDC

	BLBC	R0,30$			; If error report it
	POPR	#^M<R0,R3>		; Restore user buffer and irp address
	MOVL	R2,IRP$L_SVAPTE(R3)	; Save address of buffer
	MOVW	R1,IRP$W_BOFF(R3)	;  and requested byte count
	MOVZWL	R1,R1			; convert to longword count

.IF	NOT_DEFINED	VMS_V5
	MOVL	PCB$L_JIB(R4),R11	; Get Jib address
	SUBL	R1,JIB$L_BYTCNT(R11)	; Adjust quota count
.ENDC

	MOVAB	12(R2),(R2)		; Save addr of start of user data
	MOVL	R0,4(R2)		; Save user buffer address in 2nd
					; longword
	JMP	G^EXE$QIODRVPKT	; Queue I/O packet to start I/O routine
;
; Did he request a read of zero bytes?
;
10$:	MOVL	#SS$_NORMAL,R0		; Everything is ok
	JMP	G^EXE$FINISHIOC		; complete I/O request
;
; Come here when something goes wrong
;
30$:	POPR	#^M<R1,R3>		; Clear buffer addr and restore IRP
	JMP	G^EXE$ABORTIO		; complete I/O request

	.PAGE
	.SBTTL	PY$FDTWRITE - Function decision routine for PY control write
;++
; PY$FDTWRITE
;
; Functional Description:
;
;	This routine is called from the function decision table dispatcher
; 	to process a write physical, write logical, write virtual I/O
;	function.
;
;	The function first verifies the caller's parameters, terminating
;	the request with immediate success or error if necessary.
;	The routine then immediately start cramming the characters into
;	the associated units typeahead buffer by calling putnxtchr.
;
; Inputs:
;
;	R0,R1,R2	= Scratch
;	R3		= IRP Address
;	R4		= Address of PCB for current process
;	R5		= Device UCB address
;	R6		= Address of CCB
;	R7		= I/O function code
;	R8		= FDT Dispatch addr
;	R9,R10,R11	= Scratch
;	AP		= Address of function parameter list
;		P1(AP)	= Buffer Address
;		P2(AP)	= Buffer Size
;
; Outputs:
;
;	R0,R1,R2	= Destroyed
;	R3-R7,AP	= Preserved (pickled)
;
;
;	N O T E:
;		This routine now assumes that TW and PY's fork and DEVICE
;	locks are the same lock.  This allows use to keep from having to
;	do an extra unecessary lock acquisition.
;
;
;
; External Routines:
;
;	EXE$ABORTIO - FDT abort io routine
;	Input Parameters:
;		R0 - First longword of IOSB
;		R3 - IRP Address
;		R4 - PCB Address
;		R5 - UCB Address
;
;	EXE$FINISHIOC - FDT finish IO routine
;	Input Parameters:
;		R0 - First longword of IOSB
;		R3 - IRP Address
;		R4 - PCB Address
;		R5 - UCB Address
;
;	EXE$WRITECHK - Check access to buffer
;	Input Parameters:
;		R0 - Address of buffer
;		R1 - Size of buffer
;		R3 - IRP Address
;	Output Parameters:
;		R0,R1,R3 - Preserved
;		R2 - clear
;
;	@UCB$L_TT_PUTNXT(R5) - Port driver input character routine
;	Input Parameters:
;		R3 - character
;		R5 - UCB Address
;	Output Parameters:
;		R3 - if EQL then nothing
;		     if LSS then Burst address to output
;		     if GTR then char to output
;		R5 - UCB Address
;		R1,R2,R4 - trashed
;		R0 - Is this trashed or preserved? Documentation say preserve.
;
;--
PY$FDTWRITE::
	MOVZWL	P2(AP),R1		; Get buffer Size
	BNEQ	10$			; Is the non zero? If so, do it easy.
	BRW	160$			; Zero size buffer just finish it now

10$:
	MOVL	P1(AP),R0		; Get buffer Address
	JSB	G^EXE$WRITECHK		; Do we have access to the buffer
					; No return means no access
	PUSHR	#^M<R0,R1>		; Save input buffer address and size
	CLRL	IRP$L_MEDIA(R3)		; Clear out media field EXE$QIO stores P1 here
	MOVZWL	#SS$_NODATA, -		; Assume no echo buffer
		IRP$L_MEDIA+4(R3)	;
	MOVZWL	P4(AP),R1		; Get echo buffer size
	BEQL	19$			; EQL 0 no buffer
	MOVL	P3(AP),R0		; Get echo buffer address
	BEQL	19$			; EQL 0 no buffer
	PUSHR	#^M<R0,R3>		; Save buffer address and IRP
	JSB	G^EXE$READCHKR		; Check buffer for write access
	BLBC	R0,17$			; LBS success
	JSB	G^EXE$DEBIT_BYTCNT_ALO	; Verify quota and allocate buffer
	BLBC	R0,18$			; LBC error
	POPR	#^M<R0,R3>		; Restore buffer address and IRP
	MOVL	R2,IRP$L_SVAPTE(R3)	; Save address of buffer
	MOVW	R1,IRP$W_BOFF(R3)	;  and transfer size
	MOVAB	12(R2),(R2)		; Save address of start of user data
	MOVL	R0,4(R2)		; Save address of users buffer
	MOVB	#1,IRP$L_MEDIA(R3)	; Signal echo buffer exists
	BRB	19$			; Continue

17$:
	MOVZWL	R0,R1			; Save error reason
	POPR	#^M<R0,R3>		; Restore buffer address and IRP
	MOVL	R1,IRP$L_MEDIA+4(R3)	; Store error reason
	BRB	19$			; Continue

18$:
	MOVZWL	R0,R1			; Save error reason
	POPR	#^M<R0,R3>		; Restore buffer address and IRP
	MOVL	R1,IRP$L_MEDIA+4(R3)	; Store error reason

19$:
	POPR	#^M<R0,R1>		; Restore buffer
	MOVL	R1,R9			; Number of characters to send
	CLRL	R10			; Clear count of characters sent
	MOVL	R5,R11			; Save away PY UCB ptr
	PUSHR	#^M<R3,R4,R5>		; Save PCB, IRP,and UCB address
;
;	User request ok.
;
;	R0	-> 	Address of characters to input
;	R5	->	Address of TW's UCB
;	R8	->	Number of characters to send this time
;	R9	->	Total number of characters to send
;	R10	->	Numbers of characters already sent
;	R11	->	Address of Py's UCB
;
20$:
	SUBL3	R10,R9,R8		; Get number of characters send
	CMPL	R8,#BUFFER_SIZE		; More data than buffer can hold
	BGEQ	30$			; GEQ then use BUFFER_SIZE segment
	CMPL	R8,#1			; Test for one byte only
	BGTR	40$			; GTR more than one fill up buffer
	MOVZBL	(R0)[R10],R3		; Move single character into R3
	BRB	60$			; Now go ahead and send it
30$:
	MOVC3	#BUFFER_SIZE,(R0)[R10],-; Store data in UCB buffer
		UCB$T_PY_BUFFER(R11)	;
	MOVZBL	#BUFFER_SIZE,R8		; Number of characters in burst
	BRB	50$			; Now finish setup for transfer
40$:
	MOVC3	R8,(R0)[R10],	-	; Store data in UCB buffer
		UCB$T_PY_BUFFER(R11)	;
50$:
	MOVAB	UCB$T_PY_BUFFER(R11),R0	; Get buffer address
	MOVZBL	(R0)+,R3		; Get first character
60$:
	MOVL	UCB$L_PY_XUCB(R11),R5	; Get TW's UCB address

.IF	NOT_DEFINED	VMS_V5
	DSBINT	UCB$B_DIPL(R5)
.IF_FALSE
	DEVICELOCK		-	; Take device lock for TW device
		LOCKADDR=UCB$L_DLCK(R5),- ;
		LOCKIPL=UCB$B_DIPL(R5), - ;
		SAVIPL=-(SP),	-	;
		PRESERVE=YES		;
.ENDC

70$:
	INCL	R10			; Increment sent character count
	JSB	@UCB$L_TT_PUTNXT(R5)	; Buffer character
	BLSS	90$			; LSS burst
	BGTR	100$			; GTR single character
80$:	BBS	#TTY$V_TP_XOFF,	-	; See if XOFFED is so then
		UCB$B_TP_STAT(R5),120$	; stop input and check for echoed data
	DECL	R8			; Decrease number to send in burst
	BLEQ	110$			; LEQ block done see if request done
	MOVZBL	(R0)+,R3		; Get next character
	BRB	70$			; Send next character
90$:
	BISW	#TTY$M_TANK_BURST, -	; Signal burst
		UCB$W_TT_HOLD(R5)	;
	BRB	80$			; Continue processing
100$:
	MOVB	R3,UCB$W_TT_HOLD(R5)	; Store character in tank
	BISW	#TTY$M_TANK_HOLD, -	; Signal character in tank
		UCB$W_TT_HOLD(R5)	;
	BRB	80$			; Continue processing

;
; See if this request is done or if more to do
;
110$:
	CMPL	R10,R9			; All done
	BGEQ	120$			; GEQ done so check for echo

.IF	NOT_DEFINED	VMS_V5
	ENBINT
.IF_FALSE
	DEVICEUNLOCK		-	; More in request release lock
		LOCKADDR=UCB$L_DLCK(R5), - ; and go back and get it
		NEWIPL=(SP)+,	-	;
		PRESERVE=YES		;
.ENDC

	MOVL	P1(AP),R0		; Restore users buffer address
	BRW	20$			;

;
; See if need to start up pending read
;
120$:
	BBS	#TTY$V_TANK_HOLD, -	; If storing character in hold
		UCB$W_TT_HOLD(R5),130$	;  start output
	BBC	#TTY$V_TANK_BURST, -	; If no output burst then all done
		UCB$W_TT_HOLD(R5),140$	;  finish up
130$:
	MOVL	4(SP),R3		; Get IRP address
	TSTL	IRP$L_MEDIA(R3)		; See if echo buffer to deal with
	BNEQ	ECHO_PROCESSING		; NEQ 0 have echo buffer
	BBC	#UCB$V_BSY,	-	; PY ready to take data
		UCB$W_STS(R11),140$	;
	MOVL	UCB$L_IRP(R11),R3	; Get IRP address
	MOVL	R11,R5			; Get PY UCB address
	JSB	G^IOC$INITIATE		; Now start PY read
140$:

.IF	NOT_DEFINED	VMS_V5
	ENBINT
.IF_FALSE
	DEVICEUNLOCK		-	; Done release lock
		LOCKADDR=UCB$L_DLCK(R11), - ; NOTE PY & TW lock are the same
		NEWIPL=(SP)+		;
.ENDC

	MOVL	UCB$L_PY_XUCB(R11),R5	; Get TW UCB address
	BBS	#TTY$V_TP_XOFF,	-	; See if XOFFED report this special
		UCB$B_TP_STAT(R5),170$	; case
;
; Finish up the read
;
150$:
	POPR	#^M<R3,R4,R5>		; Restore IRP, PCB, and UCB
160$:
	INSV	R10,#16,#16,R0		; Move number of bytes INPUT
	MOVW	#SS$_NORMAL,R0		; Everything is just fine
	MOVL	IRP$L_MEDIA+4(R3),R1	;
	JMP	G^EXE$FINISHIO		; Complete the I/O request

;+
; Special code to deal with input while xoffed.
;-
	TTY$M_TP_XOFF = 8
	TTY$V_TP_XOFF = 3
	ASSUME	TTY$M_TP_XOFF EQ TTY$M_TP_DLLOC+4
	ASSUME  TTY$V_TP_XOFF EQ TTY$V_TP_DLLOC+1

170$:
	POPR	#^M<R3,R4,R5>		; Restore IRP, PCB, and UCB
	INSV	R10,#16,#16,R0		; Move number of bytes INPUT
	MOVW	#SS$_DATAOVERUN,R0	; Cannot input more data
	MOVL	IRP$L_MEDIA+4(R3),R1	;
	JMP	G^EXE$FINISHIO		; Complete the I/O request

	.SBTTL	ECHO_PROCESSING - handle write with echo buffer
;++
; Functional Description
;
;    This code deals with the special case where the users wishes to
;    pick up echo characters from an input burst.  The number of characters
;    is limited to the constant BUFFER_SIZE.  If the burst is larger than
;    the supplied buffer an attempt will be made to pick up the rest using
;    the STARTIO path.
;
; Inputs:
;
;    R5  -> TW UCB
;    R10 -> Number of bytes input
;    R11 -> PY UCB
;
; Stack Contents:
;
;    SP - OLD IPL
;    R3 - current requests IRP
;    R4 - PCB address
;    R5 - PY UCB address
;
; Notes:
;
;    Routine is entered holding the IOLOCK8 which is the device lock for both
;    PY & TW device.  UCB$L_DEVDEPND3 holds the number of bytes that can be read
;    and UCB$L_DEVDEPND4 holds the status of the operations and the numbers of
;    byte actually echoed.
;
;--
ECHO_PROCESSING:
	MOVL	4(SP),R3			; Get IRP address
	MOVZWL	IRP$W_BCNT(R3),R8		; Get buffer size
	MOVL	@IRP$L_SVAPTE(R3),R9		; Get buffer address

;
; At this point the registers oF interest are
;
;
;	R5	->	TW UCB
;	R8	->	Buffer size
;	R9	->	Echo buffer address
;	R10	->	Number of bytes input
;	R11	->	PY UCB address
;

ECHO_OUT_LOOP:
	FFS	#0,#6,UCB$W_TT_HOLD+1(R5),R3	; Find output states
	CASE	R3,TYPE=B,<-			; Dispatch
		ECHO_PREEMPT,-			; Send preempt character
		ECHO_STOP,-			; Stop output this should not be used
		ECHO_CHAR,-			; Character in tank
		ECHO_BURST>			; Bust of data

	BICB	#UCB$M_INT,UCB$W_STS(R5)	; Clear interupt expected
	JSB	@UCB$L_TT_GETNXT(R5)		; See if more data
	BLSS	ECHO_START_BURST		; Output burst
	BGTR	ECHO_BUFFER_CHAR		; Output single character
	BRW	ECHO_DONE			; All done finish up

ECHO_START_BURST:				; Signal new burst
	BISW	#TTY$M_TANK_BURST,	-	;
		UCB$W_TT_HOLD(R5)		;

ECHO_BURST:					; Output a burst of data
	CMPW	UCB$W_TT_OUTLEN(R5),R8		; Is buffer too small
	BGTR	10$				; Yes handle it
	MOVW	UCB$W_TT_OUTLEN(R5),R3		; Large enough so output all
	BRB	20$				; Now move it

10$:
	MOVW	R8,R3				; Move all we have space for

20$:
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers destroyed by MOVC
	MOVC	R3,@UCB$L_TT_OUTADR(R5),(R9)	; Move data
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	ADDL2	R3,R9				; Move buffer pointer
	ADDL2	R3,UCB$L_TT_OUTADR(R5)		; Move terminal buffer pointer
	SUBW2	R3,R8				; Decrement out buffer size
	SUBW2	R3,UCB$W_TT_OUTLEN(R5)		; Decrement terminal output size
	BNEQ	30$				; Still more data in burst
	BICW	#TTY$M_TANK_BURST,	-	; Got whole burst clear burst
		UCB$W_TT_HOLD(R5)		;  flag

30$:
	BRB	ECHO_CONTINUE			; Continue porcessing

ECHO_STOP:					; Signal a stop this routine
						;  should never be called
	BICB	#UCB$M_INT,UCB$W_STS(R5)	;
	BRB	ECHO_DONE			; Stop output of data

ECHO_PREEMPT:					; Output a preempt character
	MOVB	UCB$W_TT_HOLD(R5),R3		; Get character from hold
	BICW	#TTY$M_TANK_PREMPT,	-	; Clear preemption indication
		UCB$W_TT_HOLD(R5)		;
	BRB	ECHO_BUFFER_CHAR		; Buffer the character

ECHO_CHAR:					; Output a single character
	MOVB	UCB$W_TT_HOLD(R5),R3		; Get character from hold
	BICW	#TTY$M_TANK_HOLD,	-	; Clear holding character flag
		UCB$W_TT_HOLD(R5)		;
						; Fall through and buffer character

ECHO_BUFFER_CHAR:				; General routine used to buffer
						;  single character
	MOVB	R3,(R9)				; Store character
	INCL	R9				; Bump buffer pointer
	DECL	R8				; Decrement buffer size

ECHO_CONTINUE:					; Look for more echo data
	TSTL	R8				; See if more room for data
	BLEQ	ECHO_DONE			; LEQ 0 all done
	BRW	ECHO_OUT_LOOP			; Go look for more data

ECHO_DONE:					; No more ECHO characters or
						;  buffer full.
	MOVL	4(SP),R3			; Get IRP address
	MOVZWL	#SS$_NORMAL,IRP$L_MEDIA+4(R3)	; Signal got echoed data
	SUBW3	R8,IRP$W_BCNT(R3),	-	; Compute number of bytes echoed
		IRP$L_MEDIA+6(R3)		;
	MOVW	#SS$_NORMAL,IRP$L_MEDIA(R3)	; Assume not XOFFed
	MOVW	R10,IRP$L_MEDIA+2(R3)		; Store number of bytes input
	BBC	#TTY$V_TP_XOFF,		-	; See if need to signal XOFF
		UCB$B_TP_STAT(R5),10$		;
	MOVW	#SS$_DATAOVERUN,IRP$L_MEDIA(R3)	; XOFFed signal data overrun

10$:
	MOVL	R11,R5				; Get PY UCB address in R5
	JSB	G^COM$POST			; Post IRP
	BBC	#UCB$V_BSY,UCB$W_STS(R5),20$	; If no I/O pending for PY then done
	MOVL	UCB$L_PY_XUCB(R5),R11		; Get TW UCB address
	BBC	#UCB$V_INT,UCB$W_STS(R11),20$	; Any output for TW device
	MOVL	UCB$L_IRP(R5),R3		; Get IRP
	JSB	G^IOC$INITIATE			; Start PY read

20$:
.IF	NOT_DEFINED	VMS_V5
	ENBINT
.IF_FALSE
	DEVICEUNLOCK			-	; Unlock the device
		LOCKADDR=UCB$L_DLCK(R5), -	;
		NEWIPL=(SP)+			;
.ENDC
	POPR	#^M<R3,R4,R5>			; Clean up stack
	JMP	G^EXE$QIORETURN			; Return all done

	.PAGE
	.SBTTL	PY$CANCEL - Cancel the IO on the PY device
;++
;
; Functional Description:
;
;	This routine is entered to stop io on a PY unit.  If this is the last
;	deassign on the PY device, issue a CLASS_DISCONNECT on our associated
;	TW device to get it away from any processes using it.
;
; Inputs:
;
;	R2 = Negative of the Channel Number,
;		also called channel index number
;	R3 = Current IO package address
;	R4 = PCB of canceling process
;	R5 = UCB Address
;	R8 = CAN$C_CANCEL on CANCEL IO or CAN$C_DASSGN on DEASSIGN
;
; Outputs:
;	Everything should be preserved
;--
PY$CANCEL::					; Cancel PY usage
	JSB	G^IOC$CANCELIO			; Call the cancel routine
	BBC	#UCB$V_CANCEL,UCB$W_STS(R5),10$	; Branch if not for this guy
	MOVQ	#SS$_ABORT,R0			; Status is request canceled
	BICW	#<UCB$M_BSY!UCB$M_CANCEL>,-	;
		UCB$W_STS(R5)			; Clear unit status flags
	JSB	G^IOC$REQCOM			; Complete request
10$:	TSTW	UCB$W_REFC(R5)			; Last Deassign
	BNEQ	100$				; No, just exit
;
; Last DEASSIGN we need to get rid of AST's
;
	pushr	#^M<r2, r3, r4, r5, r7>
	MOVL	R2,R6				; Copy channel index for COM$FLUSHATTNS
	movl	UCB$L_PY_XUCB(r5), r5		; Switch to TW UCB
	beql	20$				; if not there, skip
	MOVAB	UCB$L_TW_XON_AST(R5),R7		; Get XON list head address
	TSTL	(R7)				; Any ast to deliver
	BEQL	11$				; EQL 0 do not flush it
	JSB	G^COM$FLUSHATTNS		; Flush it
11$:	MOVAB	UCB$L_TW_XOFF_AST(R5),R7	; Get XOFF list head address
	TSTL	(R7)				; Any ast to deliver
	BEQL	12$				; EQL 0 do not flush it
	JSB	G^COM$FLUSHATTNS		; Flush it
12$:	MOVAB	UCB$L_TW_SET_AST(R5),R7		; Get SET_LINE list head address
	TSTL	(R7)				; Any ast to deliver
	BEQL	13$				; EQL 0 do not flush it
	JSB	G^COM$FLUSHATTNS		; Flush it
;
; Do a DISCONNECT on the TW device.
;
13$:
	clrl	UCB$L_TW_XUCB(r5)		; Clear backlink to PY device
	bisl2	#UCB$M_DELETEUCB, UCB$L_STS(r5) ; Set it to go bye-bye
	bicw2	#UCB$M_ONLINE,UCB$W_STS(R5)	; Mark offline
	bicb2	#UCB$M_INT, UCB$W_STS(r5)	; Don't expect interrupt
	movl	UCB$L_TT_LOGUCB(r5), r1		; Look at logical term UCB
	tstw	UCB$W_REFC(r1)			; See if TW has any references
	bneq	15$				; If so, go and do disconnect
	jsb	G^IOC$DELETE_UCB		; if not, delete the UCB
	brb	20$
15$:	clrl	r0				; indicate that we must hangup
	movl	UCB$L_TT_CLASS(r5), r1
	jsb	@CLASS_DISCONNECT(r1)		; Force disconnect
20$:	popr	#^M<r2, r3, r4, r5, r7>		; Switch back to PY UCB
	clrl	UCB$L_PY_XUCB(r5)		; Clear link to deleted TW
	bisl2	#UCB$M_DELETEUCB, UCB$L_STS(r5)	; Set our own delete bit
100$:	rsb

	.PAGE
	.SBTTL PY$INITIAL - Initialize Pseudo terminal interface

;++
; PY$INITIAL - Initialize the interface
;
; Functional Description:
;
;	This routine is entered at device connect time and power recovery.
;	There isn't much to do to the device.
;
; Inputs:
;
;	R4 = The devices CSR  (but there is no csr!)
;	R5 = address of IDB
;	R6 = address of DDB
;	R7 = address of CRB
;
; Outputs:
;
;	All registers preserved
;
;--
PY$INITIAL::
	RSB

	.PAGE
	.SBTTL	PY$CLONE_INIT - initialize the unit
;++
; PY$CLONE_INIT - Initialize new PY device
;
; Functional Description:
;
;	Main thing we do here is clone up an associated terminal device
;	and initialize fields in the two new UCB's.
;
; Inputs:
;
;	R5	= Address of UCB
;
; Outputs:
;
;	All preserved
;--

PY$CLONE_INIT::

;+ ---
;	Ignore inits on UNIT #0 (the template PY UCB)
;- ---
	TSTL	UCB$W_UNIT(R2)			;UNIT #0??
	BNEQ	10$				;No: Initialize
	RSB					;Yes: Return

10$:	PUSHR	#^M<R0,R1,R2,R4,R6,R7,R8>
	Bicl2	#UCB$M_DELETEUCB,UCB$L_STS(R2)	; Clear ucbdelete - dec
	Movl	R2,R5
;
; Find the associated device.
;
; NOTE: We can't call IOC$SEARCHDEV because it expects the string to
;	be accessible from the previous access mode. (It executes the
;	prober instruction with mode=#0). I don't know how to make the
;	string accessible from the previous access mode cleanly, but I
;	do know how to move most of IOC$SEARCHDEV into the py driver.
;
	MOVAL	G^IOC$GL_DEVLIST-DDB$L_LINK,-	; Get address of i/o database
		R8				; listhead
	CLRL	R6				; Desired mate = PTY UNIT 0
	MOVAB	L^TWSTRING,R7			; String address for TWA
	MOVL	#TWLENGTH,R4			; String length
	BSBW	SEARCHDEV			; Find the DDB
	BNEQ	20$				; Device not found
	BSBW	SEARCHUNIT			; Search for specific unit
	BNEQ	30$				; unit found
20$:	POPR	#^M<R0,R1,R2,R4,R6,R7,R8>	; NOT FOUND: Return
	RSB

;
; Create the PTY, R1 has template UCB of TW device
;
30$:	PUSHL	R5				; Save R5
	MOVL	UCB$L_DDB(R5),R0		; Find UNIT #0 UCB FOR PY DEV.
	MOVL	DDB$L_UCB(R0),R0
	MOVL	R1,R5				; R5 = UCB to CLONE
	JSB	G^IOC$CLONE_UCB			; Clone UCB

	MOVL	R2,R1				; Put PTY UCB back into R1
	POPL	R5				; Restore R5
	BLBS	R0,40$				; WIN!!! (big deal.)
;+ ---
;	CREATE_UCB failed, mark our PY device offline
;- ---
	BICW2	#UCB$M_ONLINE,UCB$W_STS(R5)	; Mark offline
	BRW	100$				; And return
;+ ---
;	PTY UCB created successfully, link the UCBs together
;- ---
40$:	MOVL	R1,UCB$L_PY_XUCB(R5)		; Store associated UCB
	MOVL	R5,UCB$L_TW_XUCB(R1)		; Store the other one back
	CLRL	UCB$L_PID(R1)			; Clear the owner PID in PTY
	CLRW	UCB$W_REFC(R1)			; Reference count is ZERO
	Bicl2	#UCB$M_DELETEUCB,UCB$L_STS(R1)	; Inhibit deletion
	MOVW	UCB$W_UNIT(R1),-		; Set associated TW unit
		UCB$L_DEVDEPEND(R5)		; number in PY devdepend
;+ ---
;	Call the PTY unit init routine
;- ---
	MOVL	UCB$L_DDT(R1),R0		; Get DDT
	MOVL	DDT$L_UNITINIT(R0),R0		; Get Unit Init Addr in DDT
	CMPL	R0,#IOC$RETURN			; Null Address??
	BNEQ	50$				; No: Call it
	MOVL	UCB$L_CRB(R1),R0		; Yes: Look in the CRB
	MOVL	CRB$L_INTD+VEC$L_UNITINIT(R0),R0
	BEQL	100$				; No: Unit init routine

50$:	PUSHL	R5				; Save R5
	MOVL	R1,R5				; R5 = PTY UCB
	JSB	(R0)				; CALL THE UNIT INIT ROUTINE
	MOVL	G^CTL$GL_PCB,R4			; Use current PCB
	MOVL	UCB$L_ORB(R5),R0		; Fetch ORB address
	MOVL	PCB$L_UIC(R4),ORB$L_OWNER(R0)	; Set device owner
	BISB	#ORB$M_PROT_16,ORB$B_FLAGS(R0)	; Indicate using SOGW device protection
	BICW	#^XFF,ORB$W_PROT(R0)		; Make device S:O:RWLP
 	POPL	R5				; Restore R5

100$:	POPR	#^M<R0,R1,R2,R4,R6,R7,R8>
	RSB

	.PAGE
	.SBTTL	DDB finding Routines
;++
; SearchDev - Search for device DDB
;
; This routine is called to search the device database for a DDB.
; This is the first step in finding another devices UCB.
;
; This routine copied out of IOC$SEARCHDEV in IOSUBPAGD
;
; Inputs:
;
;	R8 = DDB Head
;	R7 = Address of String
;		String = ddc format: example = /TTA/
;	R4 = Length of string
;
; Outputs:
;
;	R8 = DDB of desired device if EQL, otherwise not found
;	R0 is trashed
;	R1 is trashed
;--
SEARCHDEV:				; Search for device name
10$:	MOVL	DDB$L_LINK(R8),R8	; Get address of next ddb
	BEQL	20$			; If eql end of list
	MOVAL	DDB$T_NAME(R8),R0	; Get address of generic device name
	MOVB	(R0)+,R1		; Calculate len of string to compare
	CMPB	R1,R4			; Length of names match?
	BNEQ	10$			; If neq no
	CMPC	R4,(R0),(R7)		; Compare device names
	BNEQ	10$			; If neq names do not match
	RSB
20$:	INCL	R8			; indicate search failure
	RSB

	.SBTTL	UCB finding routine
;++
; SEARCHUNIT - Subroutine to search for UCB given DDB
;
; Given the DDB of a device, get the UCB and run down the ucb list until
; we get the ucb with the desired unit number.  This code is taken out of
; IOC$SEARCHDEV in IOSUBPAGD.
;
; Inputs:
;
;	R8 = DDB of device
;	R6 = unit number of desired UCB
;
; Outputs:
;
;	R1 = UCB of device if NEQ, otherwise not found
;	R0 is trashed
;
;--
SEARCHUNIT:				; Search for unit number
	MOVAL	DDB$L_UCB-UCB$L_LINK(R8),-
		R1			; Get address of next ucb address
10$:	MOVL	UCB$L_LINK(R1),R1	; Get Address of next ucb
	BEQL	40$			; If EQL then end of list
	CMPW	R6,UCB$W_UNIT(R1)	; Unit number match?
	BEQL	30$			; If eql yes
	BRB	10$
30$:	MOVL	#SS$_NORMAL,R0		; Indicate match
40$:	RSB

	.PAGE
	.SBTTL	PY$STARTIO - Device Startio routines
;++
; PY$STARTIO	- Start Input on idle device
;
; Functional Description:
;
;	If after the read FDT routines are done and nobody is doing
;	anything on the device (UCB$V_BSY = 0) then call the start io
;	routine.
;
; Called from:
;
;	Called from any one of five places:
;	- The EXE$QIODRVPKT in the PY FDT READ routine
;		which calls EXE$INSIOQ which calls IOC$INITIATE
;	- The IOC$REQCOM at the end of this PY startio routine
;		which calls IOC$INITIATE
;	- The TW startio routine which calls IOC$INITIATE
;	- The PY write fdt routine which calls IOC$INITIATE.
;		In case we must echo a character.
;	- The PY$RESUME routine which calls IOC$INITIATE.
;
; Inputs:
;
;	R3 = IRP Address
;	R5 = UCB Address
;		UCB$W_BCNT and UCB$L_SVAPTE are written by IOC$INITIATE
;
; Outputs:
;
;	R5 - UCB Address
;
;--
PY$STARTIO::
	.ENABLE LSB

	MOVL	@UCB$L_SVAPTE(R5),-		; Initialize buffer
		UCB$L_SVAPTE(R5)		;  pointers
PY_OUT_LOOP:
;
; Here R5 must point at the PY device UCB and not at
;  the UCB of the associated TW device.
;
5$:	TSTW	UCB$W_BCNT(R5)			; Any space left in rd packet
	BLEQ	50$				; No, Completed I/O
;
; Switch to terminal UCB
;
	MOVL	UCB$L_PY_XUCB(R5),R5		; Set to TW ucb
;
; Look for next output in state tank
;
; Change Case statement to reflect V4 changes in routines - DEC
;
10$:	FFS	#0,#6,UCB$W_TT_HOLD+1(R5),R3
	CASE	R3,TYPE=B,<-			; Dispatch
		PY_PREMPT,-			; Send Prempt Characte - DEC
		PY_STOP,-			; Stop output
		PY_CHAR,-			; Char in tank
		PY_BURST,-			; Burst in progress
		>
;
; No Pending Data - Look for next character
;
	BICB	#UCB$M_INT, UCB$W_STS(R5)	; Clear interrupt expected
;
; Call class driver for more output
;
	JSB	@UCB$L_TT_GETNXT(R5)	; Get the next character
	CASEB	UCB$B_TT_OUTYPE(R5),#-1,#1
1$:	.WORD	PY_START_BURST-1$	; Burst specified
	.WORD	PY_DONE-1$		; None
	BRW	BUFFER_CHAR		; Buffer the character
;
; Output queue exhausted
PY_DONE:
	MOVL	UCB$L_TW_XUCB(R5),R5	; Switch UCBs to PY UCB
	BBC	#UCB$V_BSY,-		; If not BSY then ignore
		UCB$W_STS(R5),47$	; the char
	MOVL	UCB$L_IRP(R5),R3	; Restore IRP
	CMPW	IRP$W_BCNT(R3),-	; Any characters moved
		UCB$W_BCNT(R5)
	BNEQ	50$			; Yes complete I/O
47$:	RSB
;
; read buffer exhausted
;
50$:	MOVL	UCB$L_IRP(R5),R3	; Restore IRP
	MOVW	#SS$_NORMAL,-		; Set successful completetion
		IRP$L_IOST1(R3)
	SUBW	UCB$W_BCNT(R5),-	; Update byte count
		IRP$W_BCNT(R3)
	MOVW	IRP$W_BCNT(R3),-	; Set in status
		IRP$L_IOST1+2(R3)
;
; If we wanted to here we could set the second longword of the device status
;
	CLRL	IRP$L_IOST2(R3)		; No status
	MOVQ	IRP$L_IOST1(R3),R0	; Load IOSB return values

	JMP	G^IOC$REQCOM
;
; Put the character into the read buffer
;
BUFFER_CHAR:
	MOVL	UCB$L_TW_XUCB(R5),R5	; Switch UCBs to PY UCB
	BBC	#UCB$V_BSY,-
		UCB$W_STS(R5), 60$	; If no PY IRP, ignore
	MOVB	R3,@UCB$L_SVAPTE(R5)	; Add character to buffer
	INCL	UCB$L_SVAPTE(R5)	; Bump pointer
	DECW	UCB$W_BCNT(R5)		; Show character added
60$:	BRW	PY_OUT_LOOP		; Go for another char
;
; Take care of Burst mode R5 must be TW UCB
;
PY_START_BURST:
	BISW	#TTY$M_TANK_BURST,-	; Signal burst active
		UCB$W_TT_HOLD(R5)
;
; Continue burst
;
PY_BURST:
	MOVL	UCB$L_TW_XUCB(R5),R1	; Save PY UCB in R1
	CLRL	R3			; Initialize output size
	CMPW	UCB$W_TT_OUTLEN(R5),UCB$W_BCNT(R1)  ; Is buffer too small?
	BGTR	61$			; Yes
	MOVW	UCB$W_TT_OUTLEN(R5),R3	; Nope, so output all
	BRB	62$
61$:	MOVW	UCB$W_BCNT(R1),R3	; Just output what we can

62$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; MOVC3 destroys these registers
	MOVC3	R3,@UCB$L_TT_OUTADR(R5),@UCB$L_SVAPTE(R1)
					; Transfer burst to the buffer
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore the registers

	ADDL2	R3,UCB$L_SVAPTE(R1)	; Update output pointer
	SUBW2	R3,UCB$W_BCNT(R1)	; Update output count
	ADDL2	R3,UCB$L_TT_OUTADR(R5)	; Update input pointer
	SUBW2	R3,UCB$W_TT_OUTLEN(R5)	; Update input count
	BNEQ	65$			; Not the last character
	BICW	#TTY$M_TANK_BURST,-
		UCB$W_TT_HOLD(R5)	; Reset burst not active
65$:	MOVL	UCB$L_TW_XUCB(R5),R5	; Swicht UCBs to PY UCB
	BRW	PY_OUT_LOOP
;
; Get a single char from tt and put in read buffer R5 = TW UCB
;
PY_CHAR:
	MOVB	UCB$W_TT_HOLD(R5),R3	; Get the next byte
	BICW	#TTY$M_TANK_HOLD,-	; Show tank empty
		UCB$W_TT_HOLD(R5)
	BRW	BUFFER_CHAR
;
; Stop the output R5 = TW UCB
;
; Deleted PY_STOP2 routine and changed bit clear to byte operation - DEC
;
PY_STOP:
	BICB	#UCB$M_INT, -
		UCB$W_STS(R5)		; Reset output active
	BRW	PY_DONE			; DON'T go for anymore
					; Or we'll get into an infinite loop
;
; Send Xon or Xoff characters, R5 = TW UCB
;
; Changed PY_XOFF and PY_XON to be PY_PREMPT - DEC
;
PY_PREMPT:
	movb	UCB$B_TT_PREMPT(r5), r3	; Pick up the character
	BICW	#TTY$M_TANK_PREMPT,-	; Reset Xoff state
		UCB$W_TT_HOLD(R5)
	BRW	BUFFER_CHAR
	.DISABLE LSB

	.SBTTL	PY$SET - Set up ATTENTION AST's
;++
; PY$FDTSET - FUNCTION DECISION ROUTINE FOR SET MODE/CHAR FUNCTIONS
;
; FUNCTIONAL DESCRIPTION:
;
; This routine is the function decision routine for SET MODE/CHAR
; functions.
;
; P4(AP)	Determines what AST get's setup.
;			1 -> XON AST
;			2 -> XOFF AST
;			3 -> SET_LINE AST
;
; INPUTS:
;
;	R3 = I/O PACKET ADDRESS
;	R4 = PCB ADDRESS OF CURRENT PROCESS
;	R5 = UCB ADDRESS
;	R6 = CCB ADDRESS FOR ASSIGNED UNIT
;	AP = ADDRESS OF ARGUMENT LIST AT USER PARAMETERS
;
; OUTPUTS:
;
;	The function is completed here by "EXE$FINISHIO".
;
; IMPLICIT OUTPUTS:
;
; 	R3,R5 ARE PRESERVED.
;--
PY$FDTSET:
	PUSHL	R5
	MOVZWL	#SS$_NOSUCHDEV,R0	; Assume no TW device
	MOVL	UCB$L_PY_XUCB(R5),R5	; Switch to TW UCB
	BEQL	SET_ABORT		; No TW UCB exit
	CASE	P4(AP),LIMIT=#1,TYPE=B,<- ; Figure out what to set
		SET_XON_AST,-		;
		SET_XOFF_AST,-		;
		SET_LINE_AST>		;
BAD_SET:
	MOVZWL	#SS$_BADPARAM,R0	; Failure reason
SET_ABORT:
	POPL	R5
	JMP	G^EXE$ABORTIO

SET_XON_AST:
	MOVAB	UCB$L_TW_XON_AST(R5),R7	; XON AST list head address
	BRB	SET_CMN			; Go to common code

SET_XOFF_AST:
	MOVAB	UCB$L_TW_XOFF_AST(R5),R7 ; XOFF AST list head address
	BRB	SET_CMN			; Go to common code

SET_LINE_AST:
	MOVAB	UCB$L_TW_SET_AST(R5),R7	; SET_LINE AST list head address

SET_CMN:
	POPL	R5
	JSB	G^COM$SETATTNAST	; Insert into AST list
	JMP	G^EXE$FINISHIOC		; Complete request

	.SBTTL	PY$FDTSENSEM - Sense mode routine
;++
; TTY$FDTSENSEM - SENSE MODE
;
; FUNCTIONAL DESCRIPTION:
;
; This routine passes the the current characteristics for SENSEMODE.
; The buffer returned is a 8 or 12 bytes depending upon users request.
;
; INPUTS:
;
;	R3 = I/O PACKET ADDRESS
;	R4 = CURRENT PCB ADDRESS
;	R5 = UCB ADDRESS
;	R6 = CCB ADDRESS
;	R7 = FUNCTION CODE
;	AP = ARG LIST FROM QIO
;
; OUTPUTS:
;
;	CONTROL IS PASSED TO EXE$ABORTIO ON FAILURE
;	OR COMPLETED VIA EXE$FINISHIO
;
; STATUS RETURNS:
;
;	SS$_NORMAL - SUCCESSFULL
;	SS$_ACCVIO - BUFFER NOT ACCESSIBLE
;
; NOTE:
;	The following code assumes that the DEVICE and FORK lock
;	for the TW device are the same.  If this changes then this
;	code is broken.
;
;--
PY$FDTSENSEM::
	BSBW	VERIFY_SENSE		; VERIFY USER STORAGE
	PUSHR	#^M<R5,R6,R7,R8,R10,R11>
	MOVL	UCB$L_PY_XUCB(R5),R5	; Switch to TW UCB
	BEQL	PY$SENSE_ERR		; Have UCB
	MOVL	UCB$L_TT_LOGUCB(R5),R5	; Switch to logical device is one exists
	BSBW	GET_DCL			; BUILD SPECIAL CHARACTERISTICS

.IF	NOT_DEFINED	VMS_V5
	DSBINT	UCB$B_DIPL(R5)
.IF_FALSE
	DEVICELOCK LOCKADDR=UCB$L_DLCK(R5),- ; Lock out TW activity
		LOCKIPL=UCB$B_DIPL(R5),-; RAISE IPL
		SAVIPL=-(SP),	-	; SAVE CURRENT IPL
		PRESERVE=YES		;
.ENDC

	MOVL	UCB$B_DEVCLASS(R5),R6	; BUILD TYPE, AND BUFFER SIZE
	MOVL	UCB$L_DEVDEPEND(R5),R7	;RETURN 1ST CHARACTERISTICS LONGWORD
	BISL3	R2,UCB$L_DEVDEPND2(R5),R8;AND 2ND LONGWORD (IF REQUESTED)
	MOVL	UCB$W_TT_SPEED-2(R5),R10; RETURN SPEED
	MOVL	UCB$B_TT_PARITY-2(R5),R11; RETURN PARITY INFO
	BICL	#^XFF000000,R11		; ZERO HIGH BYTE
	MOVW	UCB$B_TT_CRFILL(R5),R11	; AND CR/LF FILL

.IF	NOT_DEFINED	VMS_V5
	ENBINT
.IF_FALSE
	DEVICEUNLOCK LOCKADDR=UCB$L_DLCK(R5),- ; RELEASE INTERLOCK
		NEWIPL=(SP)+,	-	; RESTORE IPL
		CONDITION=RESTORE, -	;
		PRESERVE=YES		;
.ENDC

	MOVL	R6,(R1)			; RETURN USER DATA
	MOVL	R7,4(R1)		;
	CMPB	R0,#12			; DID HE ASK FOR 2ND ?
	BLSS	10$			; NO
	MOVL	R8,8(R1)		;
10$:	MOVL	R10,R0			; RETURN IOSB DATA
	MOVL	R11,R1			;
	BRW	CMN_EXIT		; EXIT RETURNING R0,R1

;	If no TW device abort with SS$_NOSUCHDEV
PY$SENSE_ERR:
	POPR	#^M<R5,R6,R7,R8,R10,R11>
	MOVZWL	#SS$_NOSUCHDEV,R0	; Save error reason
	JMP	G^EXE$ABORTIO		; Abort request

	.SBTTL	PY$FDTSENSEC - Sense char routine
;++
; TTY$FDTSENSEC - SENSE CHARACTERISTICS
;
; FUNCTIONAL DESCRIPTION:
;
; This routine passes the the permanent characteristics for SENSECHAR.
; The buffer returned is a 8 or 12 bytes depending upon users request.
;
; INPUTS:
;
;	R3 = I/O PACKET ADDRESS
;	R4 = CURRENT PCB ADDRESS
;	R5 = UCB ADDRESS
;	R6 = CCB ADDRESS
;	R7 = FUNCTION CODE
;	AP = ARG LIST FROM QIO
;
; OUTPUTS:
;
;	CONTROL IS PASSED TO EXE$ABORTIO ON FAILURE
;	OR COMPLETED VIA EXE$FINISHIO
;
; STATUS RETURNS:
;
;	SS$_NORMAL - SUCCESSFULL
;	SS$_ACCVIO - BUFFER NOT ACCESSIBLE
;
; NOTE:
;	The following code assumes that the DEVICE and FORK lock
;	for the TW device are the same.  If this changes then this
;	code is broken.
;--
PY$FDTSENSEC::				; SENSE CHAR
	BSBW	VERIFY_SENSE		; VERIFY USER STORAGE
	PUSHR	#^M<R5,R6,R7,R8,R10,R11>
	MOVL	UCB$L_PY_XUCB(R5),R5	; Switch to TW UCB
	BEQL	PY$SENSE_ERR		; Have UCB
	BSBW	GET_DCL			; BUILD SPECIAL CHARACTERISTICS

.IF	NOT_DEFINED	VMS_V5
	DSBINT	UCB$B_DIPL(R5)
.IF_FALSE
	DEVICELOCK LOCKADDR=UCB$L_DLCK(R5),- ; Lock out TW activity
		LOCKIPL=UCB$B_DIPL(R5),-; RAISE IPL
		SAVIPL=-(SP)		; SAVE CURRENT IPL
.ENDC

	MOVL	UCB$B_TT_DETYPE-1(R5),R6; BUILD TYPE, AND BUFFER SIZE
	MOVB	#DC$_TERM,R6		; BUILD DEVICE CLASS
	MOVL	UCB$L_TT_DECHAR(R5),R7	;RETURN 1ST CHARACTERISTICS LONGWORD
	BISL3	R2,UCB$L_TT_DECHA1(R5),R8;AND 2ND LONGWORD (IF REQUESTED)
	MOVL	UCB$W_TT_DESPEE-2(R5),R10; RETURN SPEED
	MOVL	UCB$B_TT_DEPARI-2(R5),R11; RETURN PARITY INFO
	BICL	#^XFF000000,R11		; ZERO HIGH BYTE
	MOVW	UCB$B_TT_DECRF(R5),R11	; AND CR/LF FILL

.IF	NOT_DEFINED	VMS_V5
	ENBINT
.IF_FALSE
	DEVICEUNLOCK LOCKADDR=UCB$L_DLCK(R5),- ; RELEASE INTERLOCK
		NEWIPL=(SP)+,	-	; RESTORE IPL
		CONDITION=RESTORE	;
.ENDC

	MOVL	R6,(R1)			; RETURN USER DATA
	MOVL	R7,4(R1)		;
	CMPB	R0,#12			; DID HE ASK FOR 2ND ?
	BLSS	10$			; NO
	MOVL	R8,8(R1)		;
10$:	MOVL	R10,R0			; RETURN IOSB DATA
	MOVL	R11,R1			;
	BRW	CMN_EXIT		; EXIT RETURNING R0,R1

;	THIS ROUTINE BUILDS DCL PRIVATE CHARACTERISTICS

GET_DCL:
	CLRL	R2			; INIT RETRUN ARGUMENT
	TSTL	UCB$L_AMB(R5)		; ANY ASSOCIATED MAILBOX?
	BEQL	10$			; NO
	BISL	#TT2$M_DCL_MAILBX,R2	; YES, SO BUILD CHARACTERISTIC
10$:	RSB

;	Common exit path for PY$FDTSENSEM and PY$FDTSENSEC

CMN_EXIT:
	POPR	#^M<R5,R6,R7,R8,R10,R11> ; RESTORE SCRATCH REGISTERS
	MOVW	#SS$_NORMAL,R0
	JMP	G^EXE$FINISHIO		; COMPLETE REQUEST IOSB WORD 0,1

;	This routine verifies that the user buffer is accessable

VERIFY_SENSE:				;
	MOVL	P1(AP),R1		; ADDRESS USER BUFFER
	IFNOWRT	#8,(R1),20$		; BR IF NO ACCESS TO QUADWORD BUFFER
	MOVL	#8,R0			; INIT DEFAULT ARGUMENT SIZE
	CLRQ	(R1)			; INIT RETURN DATA
	MOVZWL	P2(AP),R2		; GET SIZE ARGUMENT
	CMPL	R2,#12			; ROOM FOR SECOND DEVDEPEND SPECIFIED?
	BLSSU	10$			; NO
	IFNOWRT	#12,(R1),20$		; CHECK IF WRITE ACCESS
	MOVW	#12,R0			; SAVE ARGUMENT SIZE
	CLRL	8(R1)			; INIT RETURN FIELD
10$:
	RSB
20$:
	MOVZWL	#SS$_ACCVIO,R0		; SET ERROR STATUS
	JMP	G^EXE$ABORTIO		; ABORT THE IO

	.SBTTL	PY$END - End of driver
PY$END:
					; End of driver
	.END
