.title PYDRIVER Pseudo terminal driver control interface .ident "V05.02 ©'88" ;++ ; ; Facililty: ; ; VAX/VMS Pseudo Terminal Driver control interface ; ; Author of this version: ; Nick de Smith ; Applied Telematics Group ; 7 Vale Avenue ; Tunbridge Wells ; Kent TN1 1DJ ; England ; +44 892 511000, PSI%234213300154::NICK ; ; Abstract: ; ; The pseudo terminal consists of two devices: ; ; o A terminal port driver (TWDRIVER) ; o A control driver (PYDRIVER, this driver) ; ; Revision History: ; ; Edit Edit date By Why ; 02 16-Dec-88 NMdS Add in direct I/O support for READLBLK/READPBLK. ; Add in SETMODE as a NOP. ; 5.01 01-Dec-88 NMdS VMS V5.0 ; 01 24-May-88 NMdS New, from old PYDRIVER. ; ; History: ; ; The original author of PYDRIVER/PTDRIVER (or whatever) was ; Dale Moore (in November 1982, I think). Since then, many people have ; modified the drivers, including: ; Dale Moore (again!) ; D. Kashtan on several occasions ; Mark London ; Doug Davis ; Kevin Carosso for many and major changes ; and probably many others who should be acknowledged, but whose names ; are not on my original listings. ; ; I took the liberty of re-writing it simply because I couldn't ; understand what was happing any more. In addition, subtle bugs ; were imbedded in the structure, and there was much redundant data ; and code. It was time for a re-write and re-structure. ; ; When VMS V5 can along, and terminal port drivers were documented ; (sort of) I completed the re-write and tried to make sure that ; all comments are accurate and not misleading. ; ; In addition, hooks have been added to make addition of enhancements ; much easier. In particular, the PY$STARTIO routine has been generalised ; to include SETMODE support. ; ; DIRECT I/O was added because we have a network server that may run 100+ ; simultaneous sessions, and I wanted to optimise data transfer. ; To use this feature, use LOGICAL or PHYSICAL I/O operations. ; ;-- .subtitle Declarations .library "SYS$LIBRARY:LIB.MLB" .link "SYS$SYSTEM:SYS.STB" /Selective_Search ; ; External Definitions: ; $candef ; Define cancel codes $dcdef ; Define device classes/types $ddbdef ; Define DDB $ddtdef ; Define DDT $devdef ; Device characteristics $dyndef ; Dynamic structure definitions $iocdef ; Define IOC$SEARCHxxx flags $iodef ; Define IO$xxx codes for FUNCTAB $irpdef ; IRP definitions $jibdef ; Define JIB offsets $pcbdef ; Define PCB $prdef ; Define PR $ssdef ; Define System Status $ttydefs ; Define terminal driver symbols $vadef ; Define virtual address fields $vecdef ; Define vector for CRB ; ; Local definitions ; P1 = 0 ; QIO argument 1..6 P2 = 4 P3 = 8 P4 = 12 P5 = 16 P6 = 20 ; ; System buffer format ; We use simple format system buffers. The buffer type and size are set by ; EXE$ALLOCBUF (which uses IRP$x_xxx names). ; $defini BUF $def BUF$L_RB_TEXT , .blkl ; => Data in buffer (=> BUF$L_RB_DATA) $def BUF$L_RB_UVA , .blkl ; => User buffer $def BUF$W_SIZE , .blkw ; Buffer size $def BUF$B_TYPE , .blkw ; Buffer type (DYN$C_BUFIO) $def BUF$L_RB_DATA ; Start of actual buffer $def BUF$K_HDR_LENGTH ; Header length $defend BUF ASSUME BUF$B_TYPE equal IRP$B_TYPE ; System buffer type field ASSUME BUF$W_SIZE equal IRP$W_SIZE ; System buffer size field ; ; Device class for PY ; DC$_PY = DC$_MISC ; Device class is miscellaneous DT$_PY = ^xFF ; Foreign device type ; ; TW device UCB extensions. ; As the TW device is a terminal port driver, all extensions must go after ; the "standard" terminal UCB fields. ; $DEFINI UCB .=UCB$K_TT_LENGTH ; Start at end of standard (terminal) UCB $DEF UCB$L_PY_UCB .BLKL 1 ; => UCB of corresponding PY device $DEF UCB$K_TW_LEN ; New size of TW UCB $DEFEND UCB ; ; PY device UCB extensions. ; The PY (control) device is not a terminal, and therefore only needs a basic ; UCB. All extensions go after the basic UCB fields. ; $DEFINI UCB .=UCB$K_LENGTH ; Start at end of standard UCB $DEF UCB$L_TW_UCB .BLKL 1 ; => UCB of corresponding TW device $DEF UCB$K_PY_LEN ; New size of PY UCB $DEFEND UCB .subtitle Standard Tables ; ; Driver Prologue Table ; DPTAB - ; Driver Prologue Table SMP = YES ,- ; VMS V5 SMP driver END = PY$END ,- ; End of driver UCBSIZE = UCB$K_PY_LEN ,- ; Size of UCB FLAGS = ,- ; Allocate SPTE, Do not allow unload ADAPTER = NULL ,- ; No adapter NAME = PYDRIVER ; Name of driver DPT_STORE INIT DPT_STORE UCB, UCB$W_UNIT_SEED , W, 0 ; Set unit # seed to zero DPT_STORE UCB, UCB$B_FLCK , B, SPL$C_IOLOCK8 ; Fork spin lock DPT_STORE UCB, UCB$B_DIPL , B, 8 ; Device interrupt IPL (no device) DPT_STORE UCB, UCB$L_STS , L, ; Template device DPT_STORE UCB, UCB$L_DEVCHAR , L, <- ; Device charactersitics 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 2 DPT_STORE UCB, UCB$B_DEVCLASS , B, DC$_PY ; Device class is a PY DPT_STORE DDB, DDB$L_DDT , D, PY$DDT ; => Driver Dispatch Table DPT_STORE REINIT DPT_STORE CRB, CRB$L_INTD+VEC$L_INITIAL, D, PY$INITIAL ; Controller initialisation DPT_STORE END .subtitle 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 ,- ; Address of function table CANCEL = PY$CANCEL ,- ; Cancel I/O routine CLONEDUCB = PY$CLONEDUCB ; Entry to clone template ; ; Function Decision Table ; PY$FUNCTAB:: FUNCTAB ,<- ; Legal Functions READVBLK ,- ; Read virtual block READLBLK ,- ; Read logical block READPBLK ,- ; Read physical block WRITEVBLK ,- ; Write virtual block WRITELBLK ,- ; Write logical block WRITEPBLK ,- ; Write physical block SETMODE ,- ; Set mode SENSEMODE ,- ; Sense mode SENSECHAR ,- ; Sense characteristics > FUNCTAB ,<- ; Buffered I/O functions READVBLK ,- ; Read virtual block WRITEVBLK ,- ; Write virtual block > FUNCTAB PY$FDTREAD,<- ; Buffered I/O read FDT routine READVBLK ,- ; Read virtual block (never hit, see below) READLBLK ,- ; Read logical block > FUNCTAB +EXE$READ,<- ; Direct I/O FDT read routine READPBLK ,- ; Read physical block READLBLK ,- ; Read logical block > FUNCTAB PY$FDTWRITE,<- ; Write FDT routine WRITELBLK ,- ; Write logical block WRITEVBLK ,- ; Write virtual block WRITEPBLK ,- ; Write physical block > FUNCTAB +EXE$SETMODE,<- ; Set device mode SETMODE ,- ; Set mode > FUNCTAB +EXE$SENSEMODE,<- ; Sense mode/char FDT routine SENSEMODE ,- ; Sense mode SENSECHAR ,- ; Sense char > .subtitle Name of terminal device template TW_TEMPLATE: .ascic "TWA0" ; Name of template TW device .subtitle PY$FDTREAD FDT read routine ;++ ; PY$FDTREAD ; ; Functional Description: ; ; This routine is called from the function decision table dispatcher ; to process a read virtual I/O function. ; ; It will also be called for a read logical function. In this case ; we check IRP$M_BUFIO. If this is set, the function is really a ; READVBLK that has been translated to READLBLK by EXE$QIO, so we ; still treat the operation as buffered I/O. If the bit is clear, this ; is a real READLBLK, so we exit this FDT routine at let ourselves be ; caught by EXE$READ later. ; ; 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 ; which is then queued to PY$STARTIO. ; ; Inputs: ; ; R0 => FDT routine being called ; R3 => IRP for curent I/O request ; R4 => PCB for current process ; R5 => UCB (from CCB) ; R6 => CCB ; R7 = Bit number of I/O function code ; R8 => current entry in FDT ; AP => function parameter list ; P1(AP) => user buffer ; P2(AP)<0:15> = buffer size ; IPL$_ASTDEL, KERNEL mode, context of user's process. ; ; Outputs: ; ; R0-R2,R9-R11 = Destroyed ; R3-R8,AP,SP = Preserved ; IRP$L_SVAPTE(R3) => allocated system buffer ; BUF$L_RB_TEXT => start of data in system buffer ; BUF$L_RB_UVA => user buffer ; IRP$W_BOFF(R3) = Total size of system buffer ;-- PY$FDTREAD:: bbs #IRP$V_BUFIO, IRP$W_STS(r3), 5$ ; Branch if its a buffered I/O operation rsb ; No, its direct I/O so use another FDT ; (caught later by EXE$READ) ; ; This is a buffered I/O read. The actual function code (in IRP$W_FUNC) is READLBLK, ; never READVBLK, due to EXE$QIO translating READVBLK into READLBLK for non file- ; structured devices. This is the reason that the READVBLK FDT entry above is ; commented as "never hit"; the entry is there for completeness only. ; See paragraph 15, section 18.5.1 "Device Independent Pre-Processing", of the V4.4 ; ISDM for an explanation of when EXE$QIO translates function codes. ; 5$: movzwl P2(ap), r1 ; R1 = User buffer size beql 10$ ; If size is 0, do nothing movl P1(ap), r0 ; R0 => User buffer jsb g^EXE$READCHK ; Check write access to user buffer R_SAVE_MASK = ^m movl r0, r9 ; R9 => User buffer (r0 is destroyed below) pushr #R_SAVE_MASK ; Save IRP address addl #BUF$K_HDR_LENGTH, r1 ; Allow for buffer header jsb g^EXE$DEBIT_BYTCNT_ALO ; Check for quota violation, allocate buffer, ; decrement JIB byte count quota. blbc r0, 30$ ; Branch if insufficient quota popr #R_SAVE_MASK ; Restore IRP address movab BUF$L_RB_DATA(r2), BUF$L_RB_TEXT(r2) ; Save => start of user data in buffer movl r9, BUF$L_RB_UVA(r2) ; Save => user buffer movl r2, IRP$L_SVAPTE(r3) ; Save address of system buffer... movw r1, IRP$W_BOFF(r3) ; ...and total size of system buffer jmp g^EXE$QIODRVPKT ; Queue I/O packet to start I/O routine ; ; If a read request for zero bytes was made, we fake a normal completion. ; 10$: movzwl #SS$_NORMAL, r0 ; Everything is ok jmp g^EXE$FINISHIOC ; Complete I/O request ; ; Some error has occured. Terminate the I/O operation and return reason code to user. ; 30$: popr #R_SAVE_MASK ; R3 => IRP jmp g^EXE$ABORTIO ; Complete I/O request .subtitle PY$FDTWRITE FDT write routine ;++ ; PY$FDTWRITE ; ; Functional Description: ; ; This routine is called from the function decision table dispatcher ; to process a write physical, logical or 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 TW's typeahead buffer by calling CLASS_PUTNXT. ; Any echo of characters is handled by calling TW$STARTIO to maybe ; call PY$STARTIO to do the actual output. ; ; The only difference between WRITEVBLK, WRITELBLK and WRITEPBLK is ; that WRITEVBLK is flagged (and billed) as a buffered I/O, and the ; other two are charged as direct I/O. ; ; Inputs: ; ; R0 => FDT routine being called ; R3 => IRP for curent I/O request ; R4 => PCB for current process ; R5 => UCB (from CCB) ; R6 => CCB ; R7 = bit number of I/O function code ; R8 => current entry in FDT ; AP => function parameter list ; P1(AP) => user buffer ; P2(AP)<0:15> = buffer size ; IPL$_ASTDEL, KERNEL mode, context of user's process. ; ; Outputs: ; ; R0-R2,R9-R11 = Destroyed ; R3-R8,AP,SP = Preserved ;-- PY$FDTWRITE:: movzwl P2(ap), r1 ; R1 = byte count beql 30$ ; If no data, then just exit movl P1(ap), r0 ; R0 => user buffer jsb g^EXE$WRITECHK ; Check access to the buffer R_SAVE_MASK = ^m pushr #R_SAVE_MASK ; Save IRP address and PY UCB address movq r0, r9 ; R9 => user buffer, R10 = buffer length movl UCB$L_TW_UCB(r5), r5 ; R5 => TW UCB ; ; Loop through the packet ; 10$: movzbl (r9)+, r3 ; R3 = Character to output ; ; The FORK spinlock we take out here covers a multitude of sins. As we are ; an FDT routine, we must take the spinlock in order to be able to manipulate ; the UCBs of TW and PY devices. Further, in order to echo any character ; we call the TW port driver STARTIO routine. This must be called with the FORK ; spinlock already granted, and with the UCB$V_INT bit set. This in turn will ; call PY$STARTIO routine, if needed, with the correct spinlocks. When all I/O ; is finished, PY$STARTIO will clear the UB$V_INT bit for the TW device, and ; we will be able to output again. ; We must use the FORK spinlock, not the DEVICE spinlock, because we may have to ; synchronise with other processes who want to look at our UCB. The DEVICE lock ; is only used for device registers (we have none!) and for certain fields in the ; UCB. FORK locking should be used for general UCB fields. ; I decided not to stuff chunks of the buffer into the PY UCB and then process ; each chunk in turn, as I am not conviced about gaining any significant ; performance advantage. On the contrary, when used as a pseudo terminal, most ; writes to the PY device are single characters, not strings, and therefore the ; overhead of moving chunks of the write buffer into NPP actually is more ; expensive (during an average session) that just taking out the FORK lock each ; time. ; FORKLOCK - ; Stop TW UCB modification... lock = UCB$B_FIPL(r5),- ; Index of fork lock to obtain savipl = -(sp),- ; Save current IPL on stack preserve= NO ; Don't save R0 pushr #^m ; Save => data and byte count jsb @UCB$L_TT_PUTNXT(r5) ; Buffer the character popr #^m movl UCB$L_TT_PORT(r5), r0 ; R0 => TW port vector table jsb @PORT_STARTIO(r0) ; Call TW$STARTIO to start output 20$: FORKUNLOCK - ; Re-enable TW UCB modification lock = UCB$B_FIPL(r5),- ; Index of fork lock to release newipl = (sp)+ ,- ; Restore old IPL from stack condition=RESTORE ,- ; Release one use preserve= NO ; Don't save R0 ; ; Check for more characters to output ; sobgtr r10, 10$ ; If more to do, go and do it popr #R_SAVE_MASK ; R5 => PY UCB, R3 => IRP ; ; Finish up the read ; 30$: movzwl P2(ap), r0 ; R0 = Number of bytes output ashl #16, r0, r0 ; R0<16:31> = Number of bytes output movw #SS$_NORMAL, r0 ; R0<0:15> = Successful completion jmp g^EXE$FINISHIOC ; Complete the I/O request .subtitle PY$CANCEL Cancel I/O on a PY device ;++ ; PY$CANCEL ; ; Functional Description: ; ; This routine is entered to stop I/O 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 = Channel index number ; R3 => IRP ; R4 => PCB of calling process ; R5 => UCB ; R8 = Reason for call to cancel the I/O request ; CAN$C_CANCEL called from $CANCEL ; CAN$C_DASSGN called from $DASSIGN or $DALLOC ; ; Outputs: ; ; R0-R3 Destroyed ; R4-R11 Preserved ;-- PY$CANCEL:: jsb g^IOC$CANCELIO ; Call device independant cancel routine bbc #UCB$V_CANCEL, UCB$L_STS(r5), 10$ ; Skip if not for this process movq #SS$_ABORT, r0 ; Status is request canceled bicl #, UCB$L_STS(r5) ; Clear unit status flags jsb g^IOC$REQCOM ; Complete request 10$: tstw UCB$W_REFC(r5) ; Last $DASSGN ? bneq 100$ ; No, just exit ; ; This is the last deassign on a PY device. We must close down the TW device. ; R_SAVE_MASK = ^m pushr #R_SAVE_MASK movl UCB$L_TW_UCB(r5), r5 ; R5 => TW UCB beql 30$ ; If not there, skip clrl UCB$L_PY_UCB(r5) ; Clear backlink to PY device bisl2 #UCB$M_DELETEUCB, UCB$L_STS(r5) ; Mark TW UCB for deletion bicl2 #UCB$M_ONLINE, UCB$L_STS(r5) ; Mark TW offline bicl2 #UCB$M_INT, UCB$L_STS(r5) ; Say TW is not busy movl UCB$L_TT_LOGUCB(r5), r1 ; Look at logical term UCB tstw UCB$W_REFC(r1) ; See if TW has any references bneq 20$ ; If so, go and do disconnect jsb g^IOC$DELETE_UCB ; If not, delete the UCB brb 30$ 20$: clrl r0 ; Indicate that we must hangup movl UCB$L_TT_CLASS(r5), r1 ; R1 => class driver dispatch table jsb @CLASS_DISCONNECT(r1) ; Force disconnect 30$: popr #R_SAVE_MASK ; R5 => PY UCB clrl UCB$L_TW_UCB(r5) ; Clear link to deleted TW bisl2 #UCB$M_DELETEUCB, UCB$L_STS(r5) ; Set PY delete bit 100$: rsb .subtitle PY$INITIAL Initialise the interface ;++ ; PY$INITIAL ; ; Functional Description: ; ; This routine is entered at device connect time and power recovery. ; There isn't much to do to a PY device. ; ; Inputs: ; ; R4 => Unit CSR (none) ; R5 => Unit IDB ; R6 => driver DDB ; R8 => driver CRB ; ; Outputs: ; ; R0-R3 Destroyed ; R4-R11 Preserved ;-- PY$INITIAL:: rsb ; Do nothing for a PY device .subtitle PY$CLONEDUCB Initialise new PY device UCB ;++ ; PY$CLONEDUCB ; ; Functional Description: ; ; EXE$ASSIGN calls PY$CLONEUCB when a $ASSIGN system service specifies ; PYA0 (which is a template device as it has UCB$V_TEMPLATE set in UCB$L_STS). ; EXE$ASSIGN does not assign a channel to the template device itself, ; rather, it creates a copy of the template device's UCB and ORB, initialising ; and clearing certain fields as appropriate. ; ; We generate the associated TW device (by cloning from TWA0:), ; and link the PY and TW UCBs together. ; ; To initialise the generated TW UCB we call TWDRIVER at the ; UNITINIT entry point in its CRB (we can't use the DDT$L_UNITINIT ; entry as the DDT is only a template for port drivers). ; ; Inputs: ; ; R0 = SS$_NORMAL ; R2 => New, cloned _PYAn: UCB ; R3 => DDT of PYDRIVER ; R4 => PCB of caller ; R5 => Template (_PYA0:) _PYAn: UCB ; ; Outputs: ; ; R0 = Final status ; R1-R11 Preserved ; The Device Support Manual (Volume 8) for VMS V5 says that you ; only need to save R2 in a cloned UCB routine. This is not true. ; All sorts of registers are needed by EXE$ASSIGN. The safest thing ; is to save the lot, with the exception of the return status, R0. ;-- PY$CLONEDUCB:: tstw UCB$W_UNIT(r2) ; Unit #0 (template) bneq 10$ ; No, so initialise rsb ; Yes, so return R_SAVE_MASK = ^m 10$: pushr #R_SAVE_MASK ; Save registers used in search movzbl TW_TEMPLATE, r8 ; R8 = "TWA0" length movab TW_TEMPLATE+1, r9 ; R9 => "TWA0" string movzbl #IOC$M_PHY!IOC$M_ANY, r10 ; R10 = search flags jsb g^IOC$PARSDEVNAM ; Parse the device name blbc r0, 20$ ; Failed to parse name clrl r11 ; R11 => where to store lock value block jsb g^IOC$SEARCHINT ; Locate device blbc r0, 20$ ; Branch if we failed to find the device ; ; Create a new TW device. ; R5 => Template UCB of TW device (TWA0:) ; jsb g^IOC$CLONE_UCB ; Clone UCB movl r2, r1 ; R1 => cloned (new) TW UCB blbs r0, 30$ ; Branch if we cloned the UCB ; ; Attempt to clone the UCB failed, mark our PY device offline ; 20$: popr #R_SAVE_MASK ; R2 => PY UCB bicl2 #UCB$M_ONLINE, UCB$L_STS(r2) ; Mark offline brw 100$ ; And return ; ; TW UCB created successfully, link the UCBs together ; R1 => New (cloned) TW UCB ; 30$: popr #R_SAVE_MASK ; R2 => UCB of PY device movl r1, UCB$L_TW_UCB(r2) ; Store TW UCB address in PY UCB movl r2, UCB$L_PY_UCB(r1) ; Store PY UCB address in TW UCB clrl UCB$L_PID(r1) ; Clear the owner PID in TW clrw UCB$W_REFC(r1) ; Reference count is ZERO movzwl UCB$W_UNIT(r1), UCB$L_DEVDEPEND(r2) ; Save TW unit number in PY UCB ; (so that SENSEMODE can read it) bicl2 #UCB$M_DELETEUCB, UCB$L_STS(r1) ; Stop TW UCB being deleted bicl2 #UCB$M_DELETEUCB, UCB$L_STS(r2) ; Stop PY UCB being deleted ; ; Call the TW unit initialisation routine. ; As TWDRIVER is a port driver, it has no DDT unit initialisation routine as ; the DDT is a dummy template. We therefore use the address specified in the CRB. ; This is all specified in TWDRIVER. ; movl UCB$L_CRB(r1), r3 ; R3 => TW driver CRB movl CRB$L_INTD+VEC$L_UNITINIT(r3), r3 ; R3 => Unit init routine bgeq 100$ ; No unit init routine (should be there!) movl r1, r5 ; R5 => TW device UCB ; ; R2 => PY UCB ; R4 = caller's PCB ; R5 => TW UCB ; pushr #^m ; Unit initialisation routines burn R0-R3 jsb (r3) ; Call the TW unit init routine popr #^m ; Restore R0-R3 movzwl #SS$_NORMAL, r0 ; R0 = final status for EXE$ASSIGN 100$: rsb .subtitle PY$STARTIO Start I/O on an idle PY device ;++ ; PY$STARTIO ; ; Functional Description: ; ; The STARTIO routine is called whenever there is data to ; process on the PY device, and the device is idle (UCB$V_BSY = 0). ; Calling this routine will cause all queued IRPs to be processed. ; ; Called from: ; ; Called from: ; - TW$STARTIO which calls IOC$INITIATE on the PY device. ; - PY$FDTREAD which calls EXE$QIODRVPKT which calls ; EXE$INSIOQ which calls IOC$INITIATE. ; - PY$FDTWRITE routine which calls TW$STARTIO (indirectly via ; the TW PORT_STARTIO entry point) in case we need to ; echo a character. ; - PY$STARTIO which calls IOC$REQCOM (at the end) ; which calls IOC$INITIATE. ; - EXE$SETMODE which calls IOC$INITIATE. ; ; Inputs: ; ; R3 => IRP ; R5 => PY UCB ; UCB$L_IRP => current IRP ; UCB$W_BCNT = IRP$L_BCNT<0:16> (transfer byte count) ; For buffered I/O read (IRP$V_BUFIO set in IRP$W_STS): ; IRP$W_FUNC = READLBLK + modifiers (READVBLK is translated ; by SYS$QIO to READLBLK) ; UCB$L_SVAPTE => System buffer address ; For direct I/O read: ; IRP$W_FUNC = READLBLK/READPBLK + modifiers ; UCB$W_BOFF = byte offset to start of transfer in page ; UCB$L_SVAPTE => PTE that maps first page in buffer ; UCB$L_SVPN = page number of SPTE allocated to this UCB ; ; Outputs: ; ; R0-R2,R4 Destroyed ; R3,R5-R11 Preserved ;-- PY$STARTIO:: movzwl IRP$W_FUNC(r3), r1 ; R1 = entire function code movw r1, UCB$W_FUNC(r5) ; Save function in UCB for error logging extzv #IO$V_FCODE, #IO$S_FCODE, r1, r2 ; R2 = I/O function field cmpb #IO$_SETMODE, r2 ; Set mode? beql PY$SETMODE ; Yes bbs #IRP$V_FUNC, IRP$W_STS(r3), PY$READ ; Branch if a read function ; ; Unrecognised I/O function code ; We shouldn't get this far, but this is just defensive programming. ; movq #SS$_ILLIOFUNC, r0 ; Say its an illegal I/O function REQCOM ; And return ; ; Set mode ; Do nothing for the moment. This will probably be used in the future for ; setting special actions, such as attention ASTs. ; PY$SETMODE:: movq #SS$_NORMAL, r0 ; Return success REQCOM ; PY$READ:: bbc #IRP$V_BUFIO, IRP$W_STS(r3), GET_NEXT_DATA ; Branch if its a direct I/O operation ; ; Buffered I/O read ; movl @UCB$L_SVAPTE(r5), UCB$L_SVAPTE(r5) ; UCB$L_SVAPTE => system buffer text area ASSUME BUF$L_RB_TEXT equal 0 ; System data pointer must be first in buffer ; for the above movl to work. GET_NEXT_DATA: ; ; R5 => PY UCB ; tstw UCB$W_BCNT(r5) ; Any space left in read packet bgtru 10$ ; Yes, try to read some more brw READ_BUFFER_EXHAUSTED ; No space in read IRP, finish up 10$: ; ; Check the "tank" for any pending output before asking the class driver ; for more data. ; movl UCB$L_TW_UCB(r5), r5 ; R5 => TW UCB ffs #0, #6, UCB$W_TT_HOLD+1(r5), r3 ; Locate next type of output case r3, type=b,< - ; Dispatch TANK_PREMPT ,- ; Send prempt character TANK_STOP ,- ; Stop output TANK_CHAR ,- ; Char in tank TANK_BURST ,- ; Burst in progress > ; ; No data found in the holding tank. Ask the class driver for more data ; for this port device. We must clear UCB$V_INT otherwise CLASS_GETNXT ; will not return any data. ; bicl #UCB$M_INT, UCB$L_STS(r5) ; Say TW not busy with output jsb @UCB$L_TT_GETNXT(r5) ; Get the next character from TW case UCB$B_TT_OUTYPE(r5),type=b,limit=#-1,displist=<- NEW_BURST ,- ; Burst specified NO_MORE_DATA ,- ; None > ; ; One character to output ; R3 = Character ; R5 => TW UCB ; movb r3, UCB$W_TT_HOLD(r5) ; Save character in tank movab UCB$W_TT_HOLD(r5), r1 ; R1 => character to output ; Fall through to character output code below ; ; One character to output ; R1 => Character to output ; R5 => TW UCB ; OUTPUT_ONE_CHAR: movl UCB$L_PY_UCB(r5), r5 ; R5 => PY UCB bbc #UCB$V_BSY, UCB$L_STS(r5), 20$ ; If no PY IRP, ignore movl UCB$L_IRP(r5), r3 ; Restore IRP bbs #IRP$V_BUFIO, IRP$W_STS(r3), 10$ ; Branch if its a buffered I/O operation ; Direct I/O movl #1, r2 ; 1 character to output jsb g^IOC$MOVTOUSER ; Move the character to the user buffer incw UCB$W_BOFF(r5) ; Offset moves one byte down buffer bicw #^cVA$M_BYTE, UCB$W_BOFF(r5) ; Make modulo page size bneq 15$ ; Branch if still some space addl #4, UCB$L_SVAPTE(r5) ; => next PTE in buffer brb 15$ ; Exit ; Buffered I/O 10$: movb (r1), @UCB$L_SVAPTE(r5) ; Add character to buffer... incl UCB$L_SVAPTE(r5) ; ...and bump pointer ; Common code 15$: decw UCB$W_BCNT(r5) ; One less byte left in buffer 20$: brw GET_NEXT_DATA ; Go for another char ; ; Send XON or XOFF characters ; R5 => TW UCB ; XON/XOFF characters are stored in the "pre-empt" buffer. As they are important ; (being flow control characters) they get output before anything else. ; TANK_PREMPT: movab UCB$B_TT_PREMPT(r5), r1 ; R1 => character to output bicw #TTY$M_TANK_PREMPT, UCB$W_TT_HOLD(r5) ; No pre-empt character now brw OUTPUT_ONE_CHAR ; ; Stop the output ; R5 => TW UCB ; As TTY$M_TANK_STOP can only be set by a port driver, and TWDRIVER does not ; use this bit, we can do very little here. Put in some defensive programming ; for future use. ; TANK_STOP: bicl #UCB$M_INT, UCB$L_STS(r5) ; Say TW now idle bicw #TTY$M_TANK_STOP, UCB$W_TT_HOLD(r5) ; Say we have stopped ; Drop through to finish up ; ; No more data to output from TW ; R5 => TW UCB ; NO_MORE_DATA: movl UCB$L_PY_UCB(r5), r5 ; R5 => PY UCB bbc #UCB$V_BSY, UCB$L_STS(r5), 10$ ; If not BSY then skip checks movl UCB$L_IRP(r5), r3 ; Restore IRP cmpw IRP$W_BCNT(r3), UCB$W_BCNT(r5) ; Any data read at all? beql 10$ ; No data read brw READ_BUFFER_EXHAUSTED ; Yes, complete I/O 10$: rsb ; Return, do not dismiss IRP ; ; Get a single char from the TW holding tank and put in read buffer ; R5 => TW UCB ; TANK_CHAR: movab UCB$W_TT_HOLD(r5), r1 ; R1 => characater to output bicw #TTY$M_TANK_HOLD, UCB$W_TT_HOLD(r5) ; Say tank now empty brw OUTPUT_ONE_CHAR ; Buffer the character for output ; ; Start burst mode (not from the tank) ; R5 => TW UCB ; NEW_BURST: bisw #TTY$M_TANK_BURST, UCB$W_TT_HOLD(r5) ; Make it look like a TANK burst ; so that we can use common code ; ; Output burst of data ; R5 => TW UCB ; UCB$W_TT_OUTLEN Length of data to output ; UCB$L_TT_OUTADDR Address of data to output ; TANK_BURST: movl r5, r4 ; R4 => TW UCB movl UCB$L_PY_UCB(r4), r5 ; R5 => PY UCB bbc #UCB$V_BSY, UCB$L_STS(r5), 20$ ; If no PY IRP, ignore movzwl UCB$W_TT_OUTLEN(r4), r2 ; Assume we output all cmpw r2, UCB$W_BCNT(r5) ; Is buffer too small? bleq 5$ ; No, so output all movzwl UCB$W_BCNT(r5), r2 ; Yes, just output what we can 5$: movl UCB$L_IRP(r5), r3 ; R3 => IRP bbs #IRP$V_BUFIO, IRP$W_STS(r3), 10$ ; Branch if its a buffered I/O operation ; Direct I/O (R2 = byte count) movl UCB$L_TT_OUTADR(r4), r1 ; R1 => Driver's buffer pushr #^m ; Must preserve R2 (byte count) jsb g^IOC$MOVTOUSER ; Copy data to user's buffer popr #^m ; Restore byte count addw r2, UCB$W_BOFF(r5) ; Update byte offset in buffer extzv #VA$V_VPN, #<16-VA$V_VPN>, UCB$W_BOFF(r5), r1 ; R1 = Number of pages we have advanced moval @UCB$L_SVAPTE(r5)[r1], UCB$L_SVAPTE(r5) ; Add 4*pagecount to PTE offset pointer bicw #^cVA$M_BYTE, UCB$W_BOFF(r5) ; Make byte offset modulo page size brb 15$ ; Update offsets ; Buffered I/O 10$: pushr #^m ; MOVC3 destroys these registers movc3 r2, @UCB$L_TT_OUTADR(r4),@UCB$L_SVAPTE(r5) ; Transfer burst to the buffer popr #^m ; Restore the registers addl r2, UCB$L_SVAPTE(r5) ; Update output pointer ; Common code 15$: subw r2, UCB$W_BCNT(r5) ; Update bytes left in IRP addl r2, UCB$L_TT_OUTADR(r4) ; Update input pointer subw r2, UCB$W_TT_OUTLEN(r4) ; Update input count bneq 20$ ; Not the last character bicw #TTY$M_TANK_BURST, UCB$W_TT_HOLD(r4) ; Say burst finished 20$: brw GET_NEXT_DATA ; ; PY read buffer exhausted or no more data available from TW output. ; R5 => PY UCB ; READ_BUFFER_EXHAUSTED: movl UCB$L_IRP(r5), r3 ; R3 => IRP subw3 UCB$W_BCNT(r5), IRP$W_BCNT(r3), r0 ; R0<0:15> = tranferred byte count bbc #IRP$V_BUFIO, IRP$W_STS(r3), 10$ ; Branch if it was direct I/O movw r0, IRP$W_BCNT(r3) ; Save final buffered I/O byte count 10$: ashl #16, r0, r0 ; R0<16:31> = byte count movw #SS$_NORMAL, r0 ; R0<0:15> = completion status clrl r1 ; No second longword of IOSB REQCOM ; Complete the I/O request and return PY$END:: ; End of driver .end