.TITLE LPDRIVER ; ; This is the ALPHA (previously called "EVAX") version of ARCH_DEFS.MAR, ; which contains architectural definitions for compiling VMS sources ; for VAX and ALPHA systems. ; EVAX = 1 ALPHA = 1 BIGPAGE = 1 ADDRESSBITS = 32 ;************************************************************************* ; ; LPDRIVER.MAR - Device driver to intercept data displayed on a ; terminal connected via the terminal class driver. ; ; 10/03/97 RLI - Modified TBI_Single useage to take the address from a ; register, as required by VMS 7.1. ; ;************************************************************************* $ADPDEF $CANDEF $CRBDEF $DCDEF $DDBDEF $DEVDEF $DYNDEF $IDBDEF $IODEF $IPLDEF $IRPDEF $ORBDEF $PCBDEF $PRDEF $PRIDEF $PRVDEF $PSLDEF $SSDEF $UCBDEF $VECDEF $BusArrayDef $IocDef $PteDef $SplCodDef $VaDef $LpDef $TtDef $TtyDefs .Page .Subtitle Unit Control Block Driver_Data $Defini UCB .=Ucb$K_Length $DEF Ucb$L_WindowSvaPte ; Address of PTE for direct I/O window. .BLKL 1 $DEF Ucb$L_Window ; Address of direct I/O window. .BLKL 1 $DEF Ucb$L_Bytes ; Number of bytes taken from buffer. .BLKL 1 Ucb$K_UcbLength = . $DEFEND UCB .Page .Subtitle Driver Tables ; Driver Prolog Table DPTAB - END = LpEnd, - ADAPTER = Null, - UCBSIZE = , - NAME = LPDRIVER, - MAXUNITS = 1, - DEFUNITS = 1, - SMP = YES, - FLAGS = Dpt$M_Svp, - ; Need a page for direct I/O. STEP = 2 DPT_STORE INIT DPT_STORE UCB,UCB$B_Flck,B,Spl$C_IoLock8 DPT_STORE UCB,UCB$B_DIPL,B,22 DPT_STORE UCB,UCB$L_DevChar,L,- DPT_STORE UCB,UCB$L_DevChar2,L,0 DPT_STORE UCB,UCB$B_DevClass,B,DC$_LP DPT_STORE UCB,UCB$B_DevType,B,DT$_LA11 DPT_STORE UCB,UCB$W_DevBufSiz,W,80 DPT_STORE UCB,UCB$L_DevDepend,L,LP$M_MechForm DPT_STORE UCB,UCB$L_DevDepend+3,B,66 DPT_STORE REINIT DPT_STORE DDB,DDB$L_DDT,D,LP$DDT DPT_STORE END ; Driver Dispatch Table DDTAB DEVNAM = LP,- JSB_START = StartIO,- FUNCTB = LpFuncTable,- CTRLINIT = ControllerInit,- UNITINIT = UnitInit,- CANCEL = CancelIo ; Function Decision Table FDT_INI LpFuncTable FDT_ACT Lp_SenseMode, FDT_ACT Lp_Read, FDT_ACT Lp_Connect, FDT_ACT Lp_DisConnect, ; Parameter list offsets. P1=4 P2=8 P3=12 P4=16 OriginalGetNext: ; The address of the class driver's GetNxt .blkl 1 ; routine is recorded here. OriginalPutNext: ; The address of the class driver's PutNxt .blkl 1 ; routine is recorded here. OriginalStartIO: ; The address of the port driver's Start I/O .blkl 1 ; routine is recorded here. OriginalPortVector: ; The address of the port driver's vector table .blkl 1 ; is recorded here so that we can restore the ; the original Start I/O connection even if ; the UCB has been deallocated. Lp_Ucb: ; Address of UCB stashed here so we can .blkl 1 ; find it again. VictimUcb: ; Address of the device we've connected to's .blkl 1 ; UCB so we can find it again. .align long ; Space for the buffer BufferSize = 65536 BufferTail: ; Data goes in the tail. .blkl 1 BufferHead: ; Data comes out the head. .blkl 1 BufferLast: ; For convenient compares. .blkl 1 BufferFirst: .blkl 1 Buffer: .blkb BufferSize ; Space to record data .align long BufferEnd: ; Label so we can detect wrap .page .Subtitle Controller Initialization Routine Driver_Code .ENABLE LSB ;****************************************************************************** ; ; Controller Initialization routine. Initializes log buffer pointers. ; ;****************************************************************************** ControllerInit: $Driver_CtrlInit_Entry ; Initialize buffer pointers. MOVAL Buffer,BufferFirst MOVAL BufferEnd,BufferLast MOVAL Buffer,BufferHead MOVAL Buffer,BufferTail CLRL VictimUcb ; Return to SYSGEN with successful status. MOVL #SS$_Normal,R0 RET .DISABLE LSB .page .Subtitle Unit Initialization Routine .ENABLE LSB ;************************************************************************** ; ; Unit Initialization routine. Calculates addresses for the window ; page allocated because we have the SVPN flag set. This page is used ; to move data from the log buffer to a user's read buffer. ; ; The address of the first log buffer variable is stashed in the ; UCB$L_SvPn field so that it can be easily seen with ANALYZE/SYSTEM. ; ;************************************************************************** UnitInit: $Driver_UnitInit_Entry ; Compute addresses of page reserved during loading. MOVL Ucb$L_Svpn(R5),R1 ; Get the page number. ASHL g^MMG$GL_Vpn_to_Va,R1,R1 ; Make it a virtaul address. BISL3 #VA$M_System,R1,Ucb$L_Window(R5) ; Add system bit and save. MNEGL g^MMG$GL_Pte_Offset_to_Va,R2 ; Build right-shift value. ASHL R2,R1,R1 ; Make virtual address a PTE offset. ADDL3 g^MMG$GL_SptBase,R1,Ucb$L_WindowSvaPte(R5) ; Make it a SvaPte and save it. ; Stash an interesting address somewhere accessible. MOVAL OriginalGetNext,Ucb$L_Svpn(R5) MOVL R5,Lp_Ucb ; Mark the device online and exit. BISL2 #Ucb$M_Online,Ucb$L_Sts(R5) MOVL #SS$_Normal,R0 RET .page .Subtitle Start I/O Routine .ENABLE LSB ;**************************************************************************** ; ; Start I/O routine. Does the actual work for the QIO. The type of QIO ; that is to be performed is specified in the IRP field Irp$L_Qio_P6 ; and is set up by the FDT routines. ; ; There are currently three types of QIOs that may be processed: ; ; - Connect: Causes the driver to search for the specified terminal and ; attach to it. The driver verifies that the specified device ; is a terminal before attaching. The terminal is specified ; using Irp$L_Qio_P1 and Irp$L_Qio_P2. P1 contains a longword ; with the .ASCIC controller name; this is conveniently ; assumed to be three characters, which nicely fits in a ; longword. P2 contains the unit number. ; ; - Disconnect: Causes the driver to disconnect from the previously ; connected terminal. If there is no terminal connected, ; the disconnect has no effect. The log buffer is flushed ; after the disconnect is performed. ; ; - Read: Causes the driver to wait for data to be present in the log ; buffer. When data is present, data is returned until either ; the QIO is completed or the log buffer becomes empty. The ; number of bytes read is returned in the high-order word of ; the first longword of the IOSB. ; ;**************************************************************************** StartIO: .JSB_Entry Input= MOVL Ucb$L_Irp(R5),R3 ; Get the IRP. ; Dispatch on the command. CMPL #1,Irp$L_Qio_P6(R3); Is it a Connect command? BEQL 10$ ; Yep. Connect. CMPL #2,Irp$L_Qio_P6(R3); Is it a Read command? BEQL 1010$ ; Yep. Do the read. CMPL #3,Irp$L_Qio_P6(R3); Is it a Disconnect command? BEQL 2000$ ; Yep. Do the disconnect. ; It's not a recognized command. Don't do anything. MOVL #SS$_BadParam,R0 CLRL R1 ReqCom ; Make sure we're not already connected to a port driver. 10$: DeviceLock LockAddr=Ucb$L_Dlck(R5),- SavIpl=-(SP),- Preserve=NO JSB DoDisconnect DeviceUnlock LockAddr=Ucb$L_Dlck(R5),- NewIpl=(SP)+,- Preserve=NO ; Find the port driver and connect to it. MOVL g^Ioc$Gl_DevList,R0 ; Address of the first DDB. 20$: TSTL R0 ; Have we reached the end of the list? BEQL 90$ ; Yes. There is no such driver. CMPL DDB$T_Name(R0),Irp$L_Qio_P1(R3) ; Is this the device? BEQL 30$ ; Yep. Now look for the unit. MOVL Ddb$L_Link(R0),R0 ; Nope. Advance to the next DDB. BRB 20$ ; Repeat until done. ; Found the DDB for the controller. Now look for the unit. 30$: MOVL DDB$L_Ucb(R0),R1 ; First UCB. 40$: TSTL R1 ; Are we at the end of the list? BEQL 90$ ; Yep. Must not be a device. CMPW Ucb$W_Unit(R1),Irp$L_Qio_P2(R3) ; Is it the unit? BEQL 50$ ; Yep. Do it. MOVL Ucb$L_Link(R1),R1 ; Advance to next UCB. BRB 40$ ; Repeat until done. ; We've found the unit. Make sure it's something we can connect to. 50$: BITL #Ucb$M_OnLine,Ucb$L_Sts(R1) ; Is it a template (offline)? BEQL 70$ ; Yep. Don't do it. BITL #Dev$M_Trm,Ucb$L_DevChar(R1) ; Is it a terminal? BEQL 80$ ; Nope. Don't do it. ; I haven't found a really good way to determine whether something is ; a DECnet device other than looking for a name of RTAx:. Lets look ; for system addresses where we expect them. BITL #^x80000000,Ucb$L_Tt_GetNxt(R1) BEQL 60$ BITL #^x80000000,Ucb$L_Tt_PutNxt(R1) BEQL 60$ BITL #^x80000000,Ucb$L_Tt_Port(R1) BEQL 60$ ; Attach to it. DeviceLock LockAddr=Ucb$L_Dlck(R5),- ; Interlock with Cancel SavIpl=-(SP) MOVL R1,VictimUcb ; Stash original UCB address MOVL Ucb$L_Tt_GetNxt(R1),OriginalGetNext ; Save GetNxt pointer. MOVAL Lp_LogOutput,Ucb$L_Tt_GetNxt(R1) ; Patch in our GetNxt routine. MOVL Ucb$L_Tt_PutNxt(R1),OriginalPutNext ; Save PutNxt pointer. MOVAL Lp_LogEchoes,Ucb$L_Tt_PutNxt(R1) ; Patch in our PutNxt routine. MOVL Ucb$L_Tt_Port(R1),R0 ; Get addr of port vector MOVL R0,OriginalPortVector ; Save port vector address. MOVL Port_StartIo(R0),OriginalStartIo ; Save start I/O address. MOVAL Lp_StartIo,Port_StartIo(R0) ; Patch in our start I/O. DeviceUnlock LockAddr=Ucb$L_Dlck(R5),- NewIpl=(SP)+ MOVL #SS$_Normal,R0 ; Return success w/UCB addr in R1. ReqCom ; It's a DECnet device. Refuse to connect with some sort of reasonable ; error code. 60$: MOVL #SS$_TermNetDev,R0 CLRL R1 ReqCom ; The device is off line and is therefore probably a template device ; such as those copied to generate pseudo terminals. Refuse to connect ; with some sort of reasonable error code. 70$: MOVL #SS$_DevOffLine,R0 CLRL R1 ReqCom ; The device is not a terminal. Refuse to connect with some sort of ; reasonable error code. 80$: MOVL #SS$_IvDevNam,R0 CLRL R1 ReqCom ; Could not find the device. 90$: MOVL #SS$_NoSuchDev,R0 CLRL R1 ReqCom ;**** Handle a Read. ; State 10: If the buffer is initially empty, we want to wait for data to ; arrive. This allows a logging program to capture data without continuously ; issuing QIOs when the terminal is idle. If the buffer is empty, go to state ; 20. If the buffer is not empty, ensure that the QIO has actually requested ; that data be moved. If the byte count is zero, go to state 100. Otherwise ; go to state 40. 1010$: CLRL Ucb$L_Bytes(R5) CMPL BufferHead,BufferTail BEQL 1020$ TSTL Ucb$L_Bcnt(R5) BNEQ 1040$ BRB 1100$ ; State 20: We don't have any data, so we need to wait a while. Grab the ; device lock so that we can WFIKPCH. WFIKPCH and then go to state 30. 1020$: DeviceLock LockAddr=Ucb$L_Dlck(R5),- SavIpl=-(SP),- Preserve=NO WFIKPCH 1030$ ; State 30: Data has arrived. Fork. If this QIO is being cancelled, ; go to state 100. Otherwise go back to state 10. 1030$: BICL2 #,Ucb$L_Sts(R5) IOFORK BITL #Ucb$M_Cancel,Ucb$L_Sts(R5) BNEQ 1100$ BRB 1010$ ; State 40: We have data in the buffer. Map the next page of the user buffer and ; invalidate the TLB. Update the SvaPte so that it points to the next page. 1040$: MOVQ @Ucb$L_SvaPte(R5),@Ucb$L_WindowSvaPte(R5) ADDL #8,Ucb$L_SvaPte(R5) movl r0,-(sp) movl ucb$l_window(r5),r0 TBI_Single r0 movl (sp)+,r0 ; State 50: Copy the next byte from the buffer and update the byte count and ; byte offset. If we need to wrap the head pointer, go to state 70. Otherwise ; go to state 60. 1050$: BISL3 Ucb$L_Window(R5),Ucb$L_Boff(R5),R0 MOVB @BufferHead,(R0) INCL Ucb$L_Boff(R5) BICL2 #^c^x1fff,Ucb$L_Boff(R5) INCL Ucb$L_Bytes(R5) DECL Ucb$L_Bcnt(R5) CMPL BufferHead,BufferLast BEQL 1070$ ; State 60: The head don't need to wrap. Increment it and go to state 80. 1060$: INCL BufferHead BRB 1080$ ; State 70: The head needs to wrap. Wrap it and go to state 80. 1070$: MOVL BufferFirst,BufferHead ; State 80: Look to see if we've taken the last byte from the buffer. If so, ; go to state 100. Otherwise go to state 90. 1080$: CMPL BufferHead,BufferTail BEQL 1100$ TSTL Ucb$L_Bcnt(R5) BEQL 1100$ ; State 90: Look to see if we crossed into a new page. If so, go to state 40. ; Otherwise go to state 50. 1090$: TSTL Ucb$L_Boff(R5) BEQL 1040$ BRB 1050$ ; State 100: We're done. Complete the QIO. 1100$: ASHL #16,Ucb$L_Bytes(R5),R0 BISL2 #SS$_Normal,R0 CLRL R1 ReqCom ;********* ; Handle a disconnect 2000$: DeviceLock LockAddr=Ucb$L_Dlck(R5),- SavIpl=-(SP) JSB DoDisconnect DeviceUnlock LockAddr=Ucb$L_Dlck(R5),- NewIpl=(SP)+ MOVL #SS$_Normal,R0 CLRL R1 ReqCom .disable lsb .page .sbttl Disconnect from the victim's UCB .enable lsb ;************ ; ; This routine disconnects from the victim's UCB, restoring it ; to normal operation. It assumes that the caller owns the ; device lock. ; ;************ DoDisconnect: .JSB_Entry Input= ; Make certain that we're connected TSTL VictimUcb BEQL 999$ ; We're connected. Do some checks to see if the UCB is still there; ; if we were connected to a DECterm that logged out, the UCB may have ; been deallocated and reallocated for some other use. MOVL VictimUcb,R1 ; Stash original UCB address MOVAL Lp_LogOutput,R0 ; Is the GetNext pointer still there? CMPL R0,Ucb$L_Tt_GetNxt(R1) BNEQ 900$ ; If not the same, reallocated. MOVAL Lp_LogEchoes,R0 ; Is the PutNext pointer still there? CMPL R0,Ucb$L_Tt_PutNxt(R1) BNEQ 900$ ; If not the same, reallocated. ; We probably don't want to check the Start I/O vector unless we save ; the original port vector address when we connect. The problem is that ; if the UCB was reallocated, we may indirect through an invalid address ; if that longword's been changed. ; We're connected. Restore the pointers. MOVL OriginalGetNext,Ucb$L_Tt_GetNxt(R1); Save GetNxt pointer. MOVL OriginalPutNext,Ucb$L_Tt_PutNxt(R1); Save PutNxt pointer. ; Even if the UCB has been deallocated, we need to restore the ; original Start I/O routine vector. The port vector table is ; shared between multiple units and probably won't be deallocated ; unless the driver is unloaded. 900$: MOVL OriginalPortVector,R0 ; Get port vector table address. MOVL OriginalStartIo,Port_StartIo(R0) ; Restore Start I/O vector. CLRL VictimUcb ; Not connected anymore. ; We're done disconnecting. Now flush the packet buffer. 999$: MOVL BufferHead,BufferTail RSB .DISABLE LSB .page .sbttl Cancel an I/O operation ;********** ; ; This routine cancels an outstanding I/O operation. If we were ; called because of a $DASSGN on the channel, the victim is disconnected. ; ;********** CancelIo: $Driver_Cancel_Entry ; Grab the device lock. DeviceLock LockAddr=Ucb$L_Dlck(R5),- SavIpl=-(SP),- Preserve=NO ; Look to see if the driver is doing any work. BITL #Ucb$M_Bsy,Ucb$L_Sts(R5) ; Doing anything? BEQL 10$ ; Nope. No QIO to cancel. ; The driver is busy. Look to see if it's working on a QIO that ; we need to cancel. MOVL Ucb$L_Irp(R5),R0 ; Grap the IRP. CMPL Irp$L_Pid(R0),Pcb$L_Pid(R4); Same process? BNEQ 10$ ; Nope. Don't cancel it. CMPL R2,Irp$L_Chan(R0) ; Same channel? BNEQ 10$ ; Nope. Don't cancel it. ; We have a QIO to cancel. Set the cancel bit and start the fork ; procedure going. BISL2 #Ucb$M_Cancel,Ucb$L_Sts(R5) PUSHR #^m MOVL Ucb$Q_FR3(R5),R3 MOVL Ucb$Q_FR4(R5),R4 JSB @Ucb$L_FPC(R5) POPR #^m ; Now look to see if we're doing a $DASSGN and therefore need to ; disconnect from the victim's UCB. 10$: CMPL #Can$C_Dassgn,R8 BNEQ 20$ ; We need to disconnect. JSB DoDisconnect ; We're done. Release the device lock and exit. 20$: DeviceUnlock LockAddr=Ucb$L_Dlck(R5),- NewIpl=(SP)+ RET .disable lsb .page .subtitle Sense Mode/Sense Characteristics FDT ;********** ; ; This routine puts up a facade of being a printer. When you show ; device, you see it's a printer. ; ;********** Lp_SenseMode: $Driver_Fdt_Entry ; Construct the first word from the page width and successful status. MOVZWL UCB$W_DevBufSiz(R5),R0 ; Get the page width ASHL #16,R0,R0 ; Move it over BISL2 #SS$_Normal,R0 ; Add in successful status ; Get the second word from the device-dependent characteristics MOVL UCB$L_DevDepend(R5),R1 Call_FinishIO .page .subtitle Read FDT processing .enable lsb ;**************** ; ; FDT preprocessing for a Read function. Verifies that the user has ; appropriate privileges and then jumps to Exe$Read. If the user doesn't ; have privileges, IllIoFunc is returned. ; ;**************** Lp_Read: $Driver_Fdt_Entry ; Make sure that the process has CMKRNL BITL #Prv$M_CmKrnl,Pcb$Q_Priv(R4) BEQL 999$ ; User can do it. Queue the request to Start I/O. MOVL #2,Irp$L_Qio_P6(R3) PUSHL P4(AP) PUSHL P3(AP) PUSHL P2(AP) PUSHL P1(AP) CALLS #4,g^Exe_Std$Read RET ; User can't do it. Return a non-informative error. 999$: MOVL #SS$_IllIoFunc,R0 Call_FinishIOC .disable lsb .page .subtitle Connect to Interrupt FDT processing .enable lsb ;***************** ; ; FDT preprocessing for ConIntRead routines. Verify that the ; process is allowed access to the device and pass it on to the Start I/O ; routine. ; ;***************** Lp_Connect: $Driver_Fdt_Entry ; Make sure that the process has CMKRNL BITL #Prv$M_CmKrnl,Pcb$Q_Priv(R4) BEQL 999$ ; Make sure P1 looks reasonable; we're looking for .ASCIC of three ; letter Device/Controller combination. CMPB #3,Irp$L_Qio_P1(R3) BNEQ 990$ ; Looks fine. Let's do it. MOVL #1,Irp$L_Qio_P6(R3) Call_QioDrvPkt ; P1 doesn't look like a controller name. Since the user has CMKRNL, ; return something mildly informative. 990$: MOVL #SS$_BadParam,R0 Call_FinishIoC ; Process doesn't have CMKRNL. We can't do that. 999$: MOVL #SS$_IllIoFunc,R0 Call_FinishIoC .disable lsb .page .subtitle Disconnect from Interrupt FDT processing .enable lsb ;***************** ; ; FDT preprocessing for ConIntWrite routines. Verify that the ; process is allowed access to the device and pass it on to the Start I/O ; routine. ; ;***************** Lp_DisConnect: $Driver_Fdt_Entry ; Make sure that the process has CMKRNL BITL #Prv$M_CmKrnl,Pcb$Q_Priv(R4) BEQL 999$ ; Looks fine. Let's do it. MOVL #3,Irp$L_Qio_P6(R3) Call_QioDrvPkt ; Process doesn't have CMKRNL. We can't do that. 999$: MOVL #SS$_IllIoFunc,R0 Call_FinishIoC .disable lsb .page .Subtitle Log Output .enable lsb ;*************** ; ; This routine is dropped in place of the class driver's GetNext ; routine. The data returned by GetNext is logged. ; ; In this routine, R5 points to the terminal's UCB, not to our UCB. ; ;*************** Lp_LogOutput: .JSB_Entry Input=,Output=,Preserve= ; Call the class driver's GetNxt routine JSB @OriginalGetNext ; Log the string, if any. JSB Lp_LogString ; Return to port driver. RSB .disable lsb .page .Subtitle Log Echoes .enable lsb ;*************** ; ; This routine is dropped in place of the class driver's PutNext ; routine. Echo data returned by PutNext is logged. ; ; In this routine, R5 points to the terminal's UCB, not to our UCB. ; ;*************** Lp_LogEchoes: .JSB_Entry Input=,Output=,Preserve= ; Call the class driver's PutNxt routine JSB @OriginalPutNext ; Log the echo, if any. JSB Lp_LogString ; Return to port driver. RSB .disable lsb .page .Subtitle Log Start I/O .enable lsb ;*************** ; ; This routine is dropped in place of the port driver's Start I/O ; routine. Initial transmit data for the port driver is logged. ; ; In this routine, R5 points to the terminal's UCB, not to our UCB. ; ;*************** Lp_StartIo: .JSB_Entry Input=,Preserve= ; Make certain that we're doing the right device. CMPL VictimUcb,R5 ; Is it the right device? BNEQ 10$ ; If not, ignore it. ; Log the initial string, if any. JSB Lp_LogString ; Call the port driver's StartIo routine. 10$: JSB @OriginalStartIo ; Return to class driver. RSB .disable lsb .page .subtitle Log a string ;***************** ; ; This routine copies the strings defined by the TTY fields of the ; UCB. If the burst is a single character, the character is assumed to ; be in R3. ; ;***************** Lp_LogString: .JSB_Entry Input=,Preserve= ; Look at the burst that was given back. TSTB Ucb$B_Tt_OuType(R5) ; What kind of burst? BGTR 10$ ; Single-character. BLSS 20$ ; Multi-character. ; No data was returned. Log nothing and return to the port driver. RSB ; We have a single-character burst. Log the character. 10$: MOVL R3,R6 JSB Lp_LogCharacter RSB ; We have a multi-character burst. Log the characters one-by-one. 20$: MOVL Ucb$L_Tt_OutAdr(R5),R1 ; Address of burst MOVZWL Ucb$W_Tt_OutLen(R5),R2 ; Length of burst ; Log the next character. 30$: MOVZBL (R1)+,R6 JSB Lp_LogCharacter ; Repeat until done. SOBGTR R2,30$ ; Return to the port driver. RSB .disable lsb .page .subtitle Log a character .enable lsb ;****************** ; ; This routine places the character in R6 into the log if there is ; room for it. ; ;****************** Lp_LogCharacter: .JSB_Entry Input=,Preserve= ; Look to see if the buffer is empty. CMPL BufferHead,BufferTail BNEQ 5$ ; The buffer is empty. We want to start the fork process. Find the ; UCB and acquire the device lock. PUSHR #^m MOVL Lp_Ucb,R5 DeviceLock LockAddr=Ucb$L_Dlck(R5),- SavIpl=-(SP),- Preserve=NO ; Look to see if there's an outstanding QIO. BITL #Ucb$M_Int,Ucb$L_Sts(R5) BEQL 3$ ; There is an outstanding QIO. Call the fork procedure. The assumption ; is that the fork IPL is equal to or lower than our IPL. MOVL Ucb$Q_FR3(R5),R3 MOVL Ucb$Q_FR4(R5),R4 JSB @Ucb$L_FPC(R5) ; Release the device lock and restore R5. 3$: DeviceUnlock LockAddr=Ucb$L_Dlck(R5),- NewIpl=(SP)+,- Preserve=NO POPR #^m ; Look to see if the buffer is full, simple case. 5$: MOVL BufferTail,R2 INCL R2 CMPL R2,BufferHead BNEQ 10$ ; Buffer is full by simple case RSB ; Check for more complicated case 10$: CMPL BufferTail,BufferLast BNEQ 20$ ; More complicated case. Look to see if buffer is full. CMPL BufferHead,BufferFirst BNEQ 20$ ; Buffer is full; tail is at end, head at beginning. RSB ; Buffer is not full. Stash the character. 20$: MOVB R6,@BufferTail ; Look to see if we need to wrap buffer tail. CMPL BufferTail,BufferLast BNEQ 30$ ; Wrap buffer tail. MOVL BufferFirst,BufferTail RSB ; Don't wrap buffer tail. 30$: INCL BufferTail RSB .disable lsb .page .Subtitle End LpEnd: .END