.ENABL LC .NLIST SYM .TITLE QCDRV - QC: Device Driver .SBTTL QCDRV - Title Page .IDENT /V03.01/ ; ; QC: device driver. This device implements a software Ethernet cable. ; Logical units form the equivalent of an Ethernet controller and are ; addressed using a 16-bit number. ; ; Author: R.W. Stamerjohn Meridian Technology Corporation ; ; Modification History: ; ; V03.01 RWS 20-Jun-1986 Initial version ; ; Macro Library Calls: ; .MCALL PKTDF$ ;Define I/O packet symbolics PKTDF$ .ASECT ; ; Define various constants used in the QC: driver. ; PN$DYN = 040000 ;Start of dynamic range BF$MAX = 20 ;Maximum buffer level MC$MAX = 6 ;Maximum multicast addresses .IIF DF R$$MPL PL$MAX = 200 ;4KW maximum pool (RSX-11M-Plus) .IIF NDF R$$MPL PL$MAX = 400 ;8KW maximum pool (RSX-11M) ; ; Define the plug structure. ; . = 0 P$LINK: .BLKW 1 ;Link to next plug P$PLUG: .BLKW 1 ;Plug number P$MULT: .BLKW MC$MAX ;Broadcast addresses P$MSLT: .BLKB 1 ;Maximum number of slots P$CSLT: .BLKB 1 ;Current number of slots P$IPKT: .BLKW 1 ;Packet chain P$SLOT: ;Message pointer(s) ; ; Define the message holding structure. ; . = 0 M$PEND: .BLKW 1 ;Pending read count M$BUFR: .BLKW 2 ;I/O buffer M$SIZE: .BLKW 1 ;Buffer size M$DSTA: .BLKW 1 ;Destination M$SRCA: .BLKW 1 ;Source address M$TYPE: .BLKW 1 ;Packet type M$LGTH: ; ; Local (RO) Data: ; .PSECT QCDRV,RW,I,LCL,REL,CON $QCTBL:: ;Ref. label .IF DF R$$MPL DDT$ QC,,NONE,,,NEW ;Define dispatch tables (RSX-11M-Plus) .IFF DDT$ QC,,NONE,,, ;Define dispatch tables (RSX-11M) .ENDC ;R$$MPL FNCTBL: .WORD IO.RLB ,QCRLB!1 ;Read message (queue) .WORD IO.RLB!QX$NOW,QCRLB!1 ;Read message (immediate) .WORD IO.WLB ,QCWLB!1 ;Write message (queue) .WORD IO.WLB!QX$NOW,QCWLB!1 ;Write message (immediate) .WORD IO.CLN ,QCDAC!1 ;Deaccess cable .WORD IO.QCX!QC$ACC,QCACC!0 ;Access cable .WORD IO.QCX!QC$DEA,QCDAC!1 ;Deaccess cable .WORD IO.QCX!QC$BRD,QCBRD!1 ;Broadcast address .WORD IO.QCX!QC$ALL,QCALL!1 ;Promiscious mode .WORD 0,0 ;EOT ; ; Local (RW) Data: ; PLUGHD: .WORD 0 ;Plug list header PLUGNO: .WORD PN$DYN ;Dynamic plug number MSGBUF: .BLKB M$LGTH ;Message buffer QCUCBA: .WORD 0 ;Our UCB address QCAPR5: .WORD 0 ;Our APR5 bias .IF NDF R$$MPL QCAPR6: .WORD 0 ;Our APR6 bias .ENDC ;R$$MPL .WORD 3 ;Allocation boundary MEMAVL: .WORD 0 ;Pointer to free hole .WORD 0 ;Size of this hole (always zero) .SBTTL QCINI * Process I/O Packet ;+ ; All QC: I/O requests are queued directly to the driver (UC.QUE=1). By ; convention, we are called with the following registers: ; ; R1 = I/O Packet address ; R4 = SCB Address ; R5 = UCB Address ;- QCINI: ;Ref. label ; ; Scan function table for a match. If none, return illegal function. ; .IF NDF R$$MPL MOV KISAR6,-(SP) ;Save current APR6 MOV QCAPR6,KISAR6 ;Map second part of driver .ENDC ;R$$MPL MOV R1,R3 ;Copy the packet address CLR -(SP) ;Set illegal function MOV #IE.IFC&377,-(SP) ; ... MOV #FNCTBL,R2 ;Get the function table 1000$: CMP (R2)+,I.FCN(R3) ;Is this correct function BEQ 2000$ ; If EQ - yes, continue TST (R2)+ ;Is this end-of-table BNE 1000$ ; If NE - no, loop BR 3100$ ;Return error ; ; Check to see if window is set correctly on logical unit. ; 2000$: MOV (R2),R2 ;Get the transfer address BIT #1,R2 ;Does function require window BNE 2100$ ; If NE - yes, continue MOV #IE.ALN&377,(SP) ;Set file already open MOV @I.LN2(R3),R5 ;Get the window address BEQ 3000$ ; If EQ - no window, continue BR 3100$ ;Return error 2100$: BIC #1,R2 ;Clear flag bit MOV #IE.NLN&377,(SP) ;Set no file open MOV @I.LN2(R3),R5 ;Get the window address BIC #1,R5 ;Clear any lock flag BNE 3000$ ; If NE - window, continue BR 3100$ ;Return error ; ; Call with processing routine with the following registers and stack. If ; the packet is internally queued, the I/O status should be set to zero. ; ; R3 = I/O Packet address ; R5 = QC: Window address ; ; (SP)+0 = return address ; (SP)+2 = I/O status (preset to IS.SUC) ; (SP)+4 = I/O status (preset to zero) ; 3000$: CLR (SP) ;Set pending status CALL (R2) ;Call processing routine 3100$: MOV (SP)+,R0 ;Get secondary status MOV (SP)+,R1 ;Get I/O status TST R0 ;Is packet finished? BEQ 9999$ ; If EQ - function still pending CALL $IOFIN ;Complete I/O function 9999$: .IF NDF R$$MPL MOV (SP)+,KISAR6 ;Restore APR6 .ENDC ;R$$MPL RETURN ;Return to caller .SBTTL QCRLB * Process IO.RLB Request ;+ ; QIO$ IO.RLB[!QX$NOW],... ; ; Process a read request to the plug. If message posted to plug, copy ; to the user buffer and complete the packet. Otherwise queue packet ; to plug unless marked immediate for completion. If so, return IE.NSF. ;- QCRLB: ;Ref. label ; ; Address check the header buffer if one exists. ; MOV #IE.SPC&377,2(SP) ;Set bad buffer MOV I.PRM+6(R3),R0 ;Get virtual address of prompt buffer BEQ 1000$ ; If EQ - no buffer MOV #6,R1 ;Set size of header buffer .IF DF A$$CHK .IF DF R$$MPL .IF DF X$$HDR MOV KISAR6,-(SP) ;Save APR6 mapping MOV $SAHDB,KISAR6 ;Map task header .IFTF ;X$$HDR CALL $CKBFR ;Address check read-only buffer .IFT ;X$$HDR MOV (SP)+,KISAR6 ;Restore APR6 mapping BCS 9999$ ; If CS - error .ENDC ;X$$HDR .IFF ;R$$MPL CALL $ACHKB ;Address check buffer BCS 9999$ ; If CS - error .ENDC ;R$$MPL .ENDC ;A$$CHK CALL $RELOC ;Relocate table address MOV R1,I.PRM+06(R3) ;Save address double word MOV R2,I.PRM+10(R3) ;Save address double word ; ; Check if a message is posted to this plug. If so, copy the buffer and ; return success status. ; 1000$: CALL QCPOP ;Pop the top message BCS 2000$ ; If CS - no message, queue packet CALL QCCOPY ;Copy the top message CALL QCFREE ;Free the top message BR 9998$ ; and exit ; ; If nowait packet, return IE.NSF. Otherwise queue packet to this plug ; and return packet pending. ; 2000$: MOV #IE.NSF&377,2(SP) ;Set no such file CMPB #QX$NOW,I.FCN(R3) ;Is this nowait packet BEQ 9999$ ; If EQ - yes, exit 2100$: ADD #P$IPKT,R5 ;Pointer to packet list 2200$: MOV R5,R4 ;Copy packet list MOV (R4),R5 ;Get next position in list BNE 2200$ ; If NE - not end, loop MOV R3,(R4) ;Link packet to end CLR (R3) ; and clear its pointer 9998$: CLR 2(SP) ;Mark packet queued/completed 9999$: RETURN ; and return. .SBTTL QCWLB * Process IO.WLB Request ;+ ; QIO$ IO.WLB[!QX$NOW],... ; ; Process a write request to the plug. If input packet posted to the ; plugs, copy from user buffer to user buffer and complete input. ; Otherwise, make a copy of the data and queue for later completion. ;- QCWLB: ;Ref. label ; ; Check that output buffer does not exceed maximum buffer (U.CW4). Return ; IE.RBG error if size is too large. ; MOV R5,-(SP) ;Save the plug MOV R3,-(SP) ;Save the I/O packet MOV #IE.RBG&377,6(SP) ;Set illegal record error MOV I.UCB(R3),R0 ;Get our UCB address CMP U.CW4(R0),I.PRM+4(R3) ;Is buffer size too large? BLO 9999$ ; If LO - yes, error ; ; Set up a dummy buffer structure using values from the I/O packet. ; ADD #I.PRM+12,R3 ;Position to I/O parameters MOV #MSGBUF+M$LGTH,R4 ;Get temporary buffer MOV -(R3),-(R4) ;Copy message type MOV P$PLUG(R5),-(R4) ;Copy source address MOV -(R3),-(R4) ;Copy destination address MOV -(R3),-(R4) ;Copy the buffer size MOV -(R3),-(R4) ;Copy the buffer address MOV -(R3),-(R4) ; ... CLR -(R4) ;Clear the access count ; ; Scan the plug list for a match on the plug type. We must check the ; plug number, promiscious flag, and multicast list. ; MOV #PLUGHD,R5 ;Get the plug header 1000$: MOV (R5),R5 ;Get the next plug BEQ 9000$ ; If EQ - end of list CMP 2(SP),R5 ;Is this our plug BEQ 1000$ ; If EQ - yes, ignore TST P$PLUG(R5) ;Is this plug promiscious? BMI 2000$ ; If MI - yes, process CMP P$PLUG(R5),M$DSTA(R4) ;Is message for this plug BEQ 2000$ ; If EQ - yes, process TST M$DSTA(R4) ;Is this a multicast address BPL 1000$ ; If PL - no, loop MOV R5,R0 ;Copy the plug address ADD #P$MULT,R0 ;Get the multicast addresses MOV #MC$MAX,R1 ;Get the number address to check 1100$: CMP (R0)+,M$DSTA(R4) ;Is this matching address BEQ 2000$ ; If EQ - yes, process SOB R1,1100$ ;Check next address BR 1000$ ;No match, loop ; ; We have a matching plug. We can do one of four things: 1) If plug ; has a queued input packet, copy from packet to packet and complete ; the input, 2) If function is immediate, ignore, 3) If copy of the ; buffer has been made, push pointer to plug, 4) Make a copy of the ; buffer and push the pointer. ; 2000$: MOV P$IPKT(R5),R3 ;Is there an input packet BEQ 2100$ ; If EQ - no, skip MOV (R3),P$IPKT(R5) ;Close up the input list CALL QCCOPY ;Copy the message to the packt BR 2200$ ; and loop to next plug 2100$: MOV (SP),R3 ;Get the I/O packet CMPB #QX$NOW,I.FCN(R3) ;Is this an immediate request BEQ 2300$ ; If EQ - yes, loop to next plug CALL QCPUSH ;Push new message into plug BCS 2300$ ; If CS - no buffer, loop 2200$: INCB 11(SP) ;Count message delivered 2300$: INCB 10(SP) ;Count plug match BR 1000$ ; and loop to next plug ; ; Determine write I/O status. Return error codes for no matching plugs ; or no message delivered, success in all other cases. User program may ; check to see all plugs got copy of the message by looking at the 2nd ; status word. ; 9000$: MOV #IE.NSF&377,6(SP) ;Set no matching plugs TSTB 10(SP) ;Did we match any plugs BEQ 9999$ ; If EQ - no, exit with error MOV #IE.RSU&377,6(SP) ;Set no messages delivered TSTB 11(SP) ;Did we deleiver any messages BEQ 9999$ ; If EQ - no, exit with error MOV #IS.SUC&377,6(SP) ;Set success status 9999$: MOV (SP)+,R3 ;Restore I/O packet MOV (SP)+,R5 ;Restore plug address RETURN ;Return to caller .SBTTL QCACC * Process IO.QCX!QC$ACC Request ;+ ; QIO$ IO.QCX!QC$ACC,... ; ; Process a request to open a new 'Ethernet controller' on the cable. The ; packet contains the plug number and input buffer level for the new plug ; structure. ;- QCACC: ;Ref. label ; ; Check for a valid plug number (0-77777). If zero, create a new plug ; number from dynamic range which is guarenteed unique at this time. ; MOV #IE.BAD&377,2(SP) ;Set bad plug number TST I.PRM+0(R3) ;Check the plug number BMI 9999$ ; If MI - cannot create broadcast BNE 2000$ ; If NE - use the preset number 1000$: INC PLUGNO ;Advance to next number BPL 1100$ ; If PL - no rollover MOV #PN$DYN,PLUGNO ;Reset dynamic range 1100$: MOV #PLUGHD,R0 ;Get plug list header 1200$: MOV (R0),R0 ;Get next plug entry BEQ 1300$ ; If EQ - found good number CMP PLUGNO,P$PLUG(R0) ;Is this matching plug BNE 1200$ ; If NE - no, loop BR 1000$ ; If EQ - yes, get new number 1300$: MOV PLUGNO,I.PRM+0(R3) ;Set a new plug number ; ; Allocate a plug stucture and link to the plug list. The length of the ; structure depends on the buffering level. ; 2000$: CMP #BF$MAX,I.PRM+2(R3) ;Does buffer range exceed maximum BHIS 2100$ ; If HIS - no, skip MOV #BF$MAX,I.PRM+2(R3) ;Set buffer range to our maximum 2100$: MOV #IE.NDR&377,2(SP) ;Set no dynamic space MOV I.PRM+2(R3),R1 ;Get the number of buffers ASL R1 ;Convert to word index ADD #P$SLOT,R1 ;Get length of plug structure MOV R3,-(SP) ;Save the I/O packet CALL QCALOC ;Allocate the structure MOV (SP)+,R3 ;Restore I/O packet BCS 9999$ ; If CS - error, exit ; ; Initialize the plug structure, link to the plug list, and store in ; the second LUT word. ; MOV R0,R2 ;Copy the plug structure address ASR R1 ;Get size in words 3000$: CLR (R2)+ ;Clear the structure SOB R1,3000$ ; and loop till done MOV R0,@I.LN2(R3) ;Store in second LUT word MOV PLUGHD,(R0) ;Link old list to new MOV R0,PLUGHD ;Link to list header MOV I.PRM+0(R3),P$PLUG(R0) ;Store plug number MOVB I.PRM+2(R3),P$MSLT(R0) ;Store input level MOV #IS.SUC&377,2(SP) ;Set success status MOV I.PRM+0(R3),4(SP) ;Return plug number 9999$: RETURN ;Return to caller .SBTTL QCDAC * Process IO.QCX!QC$DAC Request ;+ ; QIO$ IO.QCX!QC$DAC,...<> ; ; Process a request to remove a new 'Ethernet controller' from the cable. ; All resources used by the plug are returned. ;- QCDAC: ;Ref. label ; ; Fake a call to QCWLB to send deaccess message to any plugs desiring ; such a message. ; CLR -(SP) ;Preset I/O status CLR -(SP) ; ... MOV KISAR5,I.PRM+0(R3) ;Store our KISAR5 address MOV #140000,I.PRM+2(R3) ;Set dummy address MOV #2,I.PRM+4(R3) ;Set dummy length MOV #MC$DAC,I.PRM+06(R3) ;Store deaccess multicast address MOV #PT$DAC,I.PRM+10(R3) ;Store deaccess protocol type CALL QCWLB ;Write message as needed. CMP (SP)+,(SP)+ ;Ignore return status ; ; Scan the queued I/O packet list and return any packets with a status ; of IE.ABO. ; MOV R3,-(SP) ;Save the I/O packet 1000$: MOV P$IPKT(R5),R3 ;Get the next I/O packet BEQ 2000$ ; If EQ - none, skip MOV (R3),P$IPKT(R5) ;Unlink packet from chain MOV #IE.ABO&377,R0 ;Set abort status CLR R1 ; ... CALL $IOFIN ;Complete this packet BR 1000$ ; and loop for next ; ; Scan the pending buffer read list and count buffer as read. ; 2000$: CALL QCPOP ;Pop the top slot BCS 3000$ ; If CS - all messages processed CALL QCFREE ;Free the top message BR 2000$ ; and loop for more ; ; Scan the plug list for this plug and remove and deallocate the structure. ; 3000$: MOV #PLUGHD,R0 ;Get plug list header 3100$: MOV R0,R1 ;Copy the current position MOV (R1),R0 ;Get next plug entry CMP R0,R5 ;Is this matching entry? BNE 3100$ ; If NE - no, loop MOV (R0),(R1) ;Unlink this entry MOVB P$MSLT(R0),R1 ;Get the number of slots ASL R1 ; as a word index ADD #P$SLOT,R1 ;Get the structure size CALL QCDEAC ;Deallocate slot structure ; ; Mark this logical unit as deaccessed. ; MOV (SP)+,R3 ;Restore I/O packet MOV #IS.SUC&377,2(SP) ;Set success status CLR @I.LN2(R3) ;Clear the window pointer RETURN ;Return to caller .SBTTL QCBRD * Process IO.QCX!QC$BRD Request ;+ ; QIO$ IO.QCX!QC$BRD,... ; ; Set new set of multicast addresses is the plug associated with the ; issuing logical unit. A maximum of 6 addresses may be declared for ; a plug. The function wipes out any previous multicast addresses. ;- QCBRD: ;Ref. label MOV R3,R2 ;Copy the I/O packet ; ; Setup a six-level loop to check for a legal set of multi-cast addresses. ; MOV #IE.BAD&377,2(SP) ;Set bad multicast number MOV #MC$MAX,R0 ;Get the multi-cast maximum ADD #I.PRM,R2 ;Position to parameters 1000$: TST (R2)+ ;Is this address legal? BGT 9999$ ; If GT - no, error SOB R0,1000$ ;Loop until done ; ; Repeat the loop and store the new set of multi-cast addresses. ; MOV #IS.SUC&377,2(SP) ;Set success status MOV #MC$MAX,R0 ;Get the multi-cast maximum ADD #P$MULT+,R5 ;Position to multicast 2000$: MOV -(R2),-(R5) ;Copy the address SOB R0,2000$ ;Loop until done 9999$: RETURN ;Return to caller .SBTTL QCALL * Process IO.QCX!QC$ALL Request ;+ ; QIO$ IO.QCX!QC$ALL,... ; ; Set promiscious flag for plug assiciated with logical unit. A non-zero ; value means plug will receive all messages sent on the system. ;- QCALL: ;Ref. label BIC #100000,P$PLUG(R5) ;Preset no promiscious mode TST I.PRM+0(R3) ;Is this set promiscious mode BEQ 9999$ ; If EQ - no, skip BIS #100000,P$PLUG(R5) ;Set promiscious mode 9999$: MOV #IS.SUC&377,2(SP) ;Set success status RETURN ;Return to caller .SBTTL QCPOP * Get Next Plug Message ;+ ; This routine gets the next message queued to a plug and returns the ; message buffer address in R4. ; ; Call with: R5 = Plug address ; ; Returns: PSW/CC -> R4 = Message address ; PSW/CS -> No message queued ;- QCPOP: ;Ref. label TSTB P$CSLT(R5) ;Is there a queued message SEC ;Preset failure BEQ 9999$ ; If EQ - no, exit MOV P$SLOT(R5),R4 ;Get the message structure DECB P$CSLT(R5) ;Count one less slot MOVB P$CSLT(R5),R1 ;Get number active slots BEQ 9998$ ; If EQ - empty MOV R5,R0 ;Copy plug address ADD #P$SLOT,R0 ;Point to slot addresses 1100$: MOV 2(R0),(R0) ;Pop the slots up one position TST (R0)+ ;Advance to next slot SOB R1,1100$ ;Loop till finished (CC from TST) 9998$: CLC ;Set success 9999$: RETURN ;Return to caller .SBTTL QCPUSH * Queue New Plug Message ;+ ; This routine queues a new message to a plug. If the message buffer is ; the internal buffer, a message buffer is allocated. ; ; Call with: R5 = Plug address ; R4 = Message address ; ; Returns: PSW/CC -> R4 = Message address ; PSW/CS -> No message queued ;- QCPUSH: ;Ref. label CMPB P$CSLT(R5),P$MSLT(R5) ;May we queue a new message BEQ 9100$ ; If EQ - no, error CMP #MSGBUF,R4 ;Is this internal message BNE 2000$ ; If NE - no, skip MOV #M$LGTH,R1 ;Get length of memory structure CALL QCALOC ;Allocate memory structure BCS 9100$ ; If CS - error MOV R0,-(SP) ;Save allocated structure MOV R4,R1 ;Copy current structure MOV (R1)+,(R0)+ ;Copy current to new structure MOV (R1)+,(R0)+ ; ... MOV (R1)+,(R0)+ ; ... MOV (R1)+,(R0)+ ; ... MOV (R1)+,(R0)+ ; ... MOV (R1)+,(R0)+ ; ... MOV (R1)+,(R0)+ ; ... .IF DF R$$MPL MOV M$SIZE(R4),R1 ;Get the buffer size ADD #77,R1 ;Convert size to 32 word chuncks ASH #-6,R1 ; ... CALL $ALSEC ;Allocate secondary pool BCS 9000$ ;If CS - allocation error MOV #140000,R1 ;Set APR6 offset .IFF ;R$$MPL MOV M$SIZE(R4),R1 ;Get the buffer size MOV #MEMAVL-2,R3 ;Get our pool listhead CALL $ALOC1 ;Allocate from our pool BCS 9000$ ;If CS - allocation error MOV R0,R1 ;Copy allocated address BIC #177700,R1 ;Clear all but offset ADD #140000,R1 ;Set APR6 offset BIC #160000,R0 ;Clear page select ASH #-6,R0 ;Get in 32W units ADD QCAPR5,R0 ;Adjust for start of driver .ENDC ;R$$MPL MOV (SP),R3 ;Get the new message MOV R0,M$BUFR+0(R3) ;Store new message bias MOV R1,M$BUFR+2(R3) ;Store new message offset MOV M$SIZE(R4),R0 ;Get the message size MOV M$BUFR+0(R4),R1 ;Get the user buffer bias MOV M$BUFR+2(R4),R2 ;Get the user buffer offset SUB #20000,R2 ;Convert to APR5 MOV M$BUFR+2(R3),R4 ;Get our buffer offset MOV M$BUFR+0(R3),R3 ;Get our buffer bias CALL $BLXIO ;Transfer the buffers MOV (SP)+,R4 ;Restore the new message 2000$: INC (R4) ;Count message queued to plug MOVB P$CSLT(R5),R0 ;Get current slot count INCB P$CSLT(R5) ;Count message queued to plug ASL R0 ; ...as a word index ADD #P$SLOT,R0 ; ...as index to slot ADD R5,R0 ; ...as pointer to slot MOV R4,(R0) ;Queue message CLC ;Set success status BR 9999$ ; and exit 9000$: MOV (SP)+,R0 ;Restore memory structure MOV #M$LGTH,R1 ;Get length of structure CALL QCDEAC ;Deallocate structure 9100$: SEC ;Set failure 9999$: RETURN ;Return to caller .SBTTL QCFREE * Free Message ;+ ; This routine completes one plug's access to a message. If the access ; count reaches zero, the message buffers are freed. ; ; Call with: R4 = Message address ;- QCFREE: ;Ref. label DEC (R4) ;Count one less access BNE 9999$ ; If NE - still queued to some plug .IF DF R$$MPL MOV M$BUFR(R4),R0 ;Get the buffer address MOV M$SIZE(R4),R1 ;Get the buffer size ADD #77,R1 ;Convert size to 32 word chuncks ASH #-6,R1 ; ... CALL $DESEC ;Deallocate secondary pool .IFF ;R$$MPL MOV M$BUFR+0(R4),R0 ;Get the buffer bias SUB QCAPR5,R0 ;Get the bias within QC: ASH #6,R0 ;Convert to word index ADD M$BUFR+2(R4),R0 ;Add in displacement SUB #20000,R0 ;Convert to APR5 MOV M$SIZE,R1 ;Get the buffer size MOV #MEMAVL-2,R3 ;Get our pool listhead CALL $DEAC1 ; and deallocate the buffer .ENDC ;R$$MPL MOV R4,R0 ;Copy the message address MOV #M$LGTH,R1 ;Get the message length CALL QCDEAC ;Deallocate message buffer 9999$: RETURN ;Return to caller .SBTTL QCCOPY * Copy Message to Packet ;+ ; This routine copies the message to the user supplied buffer and completes ; the IO.RLB function. ; ; Call with: R4 = Message address ; R3 = I/O Packet ;- QCCOPY: ;Ref. label TST I.PRM+06(R3) ;Is there a header buffer BEQ 1000$ ; If EQ - no, skip MOV KISAR6,-(SP) ;Save current APR6 MOV M$TYPE(R4),-(SP) ;Copy header data to stack in MOV M$SRCA(R4),-(SP) ; ... case we unmap the buffer MOV M$DSTA(R4),-(SP) ; ... from APR6. MOV I.PRM+06(R3),KISAR6 ;Map the user buffer MOV I.PRM+10(R3),R2 ;Get user buffer MOV (SP)+,(R2)+ ;Copy destination MOV (SP)+,(R2)+ ;Copy source MOV (SP)+,(R2)+ ;Copy type MOV (SP)+,KISAR6 ;Restore APR6 1000$: MOV R4,-(SP) ;Save the message MOV #IS.SUC&377,-(SP) ;Preset success MOV M$SIZE(R4),R0 ;Get transfer size CMP I.PRM+4(R3),R0 ;Is user buffer too small BHIS 2000$ ; If HIS - no, skip MOV #IE.DAO&377,(SP) ;Set data overrun MOV I.PRM+4(R3),R0 ;Set new transfer size 2000$: MOV R0,-(SP) ;Save the transfer length MOV R3,-(SP) ;Save the I/O packet MOV M$BUFR+0(R4),R1 ;Get message buffer bias MOV M$BUFR+2(R4),R2 ;Get message buffer displacement SUB #20000,R2 ; as an AP5 value MOV I.PRM+2(R3),R4 ;Get user buffer displacement MOV I.PRM+0(R3),R3 ;Get user buffer bias CALL $BLXIO ;Block transfer the buffer MOV (SP)+,R3 ;Get the I/O packet MOV (SP)+,R1 ;Get the transfer length MOV (SP)+,R0 ;Get the status code CALL $IOFIN ;Complete the I/O packet MOV (SP)+,R4 ;Restore the message pointer RETURN ;Return to caller .SBTTL QCALOC * Allocate Memory ;+ ; This routine allocates memory to first the QC: private pool and then ; the system pool if QC: pool allocation fails. ; ; Call with: R1 = Memory size ; ; Returns: PSW/CC -> R0 = Memory address ; PSW/CS -> No space available ;- QCALOC: ;Ref. label MOV #MEMAVL-2,R0 ;Get our pool listhead CALL $ALOC1 ;Attempt allocation BCC 9999$ ; If CC - succeeded CALL $ALOCB ;Attempt allocation 9999$: RETURN ;Return to caller .SBTTL QCDEAC * Deallocate Memory ;+ ; This routine deallocates memory to either the QC: private pool or ; the system pool. ; ; Call with: R0 = Memory address ; R1 = Memory size ;- QCDEAC: ;Ref. label MOV #$CRAVL-2,R3 ;Preset system pool CMP #120000,R0 ;Is this in our pool BHI 1000$ ; If HI - no, system pool MOV #MEMAVL-2,R3 ;Get our pool list 1000$: CALL $DEAC1 ;Deallocate memory RETURN ;Return to caller .SBTTL QCPWF * Power Failure ;+ ; QCPWF is called whenever the system recovers from power failure or the ; device driver is loaded. We use the entry on initial load to set QC0: ; on-line. ; ; R3 = Controller index ; R4 = SCB address ; R5 = UCB address ;- .IF DF R$$MPL $QCLOA:: ;Ref. label .ENDC ;R$$MPL QCPWF: ;Ref. label TST QCUCBA ;Have we started ourselves BNE 9999$ ; If NE - yes, continue MOV R5,QCUCBA ;Store our UCB address MOV R5,U.VCB(R5) ;Store dummy VCB address ADD #U.VCB+2,U.VCB(R5) ; ... BICB #US.OFL,U.ST2(R5) ;Mark unit on-line MOV #MEMAVL-2,R1 ;Get memory list header MOV #QCPOOL,R0 ;Get start of pool ADD (R1),R0 ;Round address to boundary BIC (R1)+,R0 ; ... MOV R0,(R1) ;Store link to pool MOV U.DCB(R5),R1 ;Get our DCB pointer MOV D.PCB(R1),R1 ;Get our PCB pointer MOV P.SIZE(R1),R2 ;Get size of partition CMP #PL$MAX,R2 ;Does size exceed maximum BHIS 1000$ ; If HIS - no, skip MOV #PL$MAX,R2 ;Set size to maximum 1000$: ASH #6,R2 ;Convert to bytes ADD #120000,R2 ;Convert to APR5 SUB R0,R2 ;Get size of free space CLR (R0)+ ;Clear forward link MOV R2,(R0)+ ;Store free pool size MOV P.REL(R1),QCAPR5 ;Store starting bias .IF NDF R$$MPL MOV P.REL(R1),QCAPR6 ; ... ADD #200,QCAPR6 ;Advance to next 4KW .ENDC ;R$$MPL 9999$: RETURN ;Return to caller .SBTTL QCCAN * Cancel I/O ;+ ; QCCAN is called by the executive whenever an IO.KIL is issued or I/O ; is being rundown. This entry is a noop as IO.CLN logic will clean up ; any outstanding I/O. ; ; R0 = Address of current I/O packet ; R1 = Address of TCB of task to kill I/O ; R3 = Controller index ; R4 = SCB address ; R5 = UCB address ;- QCCAN: RETURN ;Return to caller .SBTTL QCOUT * Device Timeout ;+ ; QCTMO is called by the executive whenever the timeout counter expires ; for the device. This entry is a nop, as this condition should not occur ; for this driver. The following registers are setup on call: ; ; R0 = I/O status code (IE.DNR) ; R2 = Device CSR ; R3 = Controller index ; R4 = SCB address ; R5 = UCB address ;- QCOUT: RETURN ;Return to caller .IF DF R$$MPL .SBTTL QCKRB * KRB Status Changes .SBTTL QCUCB * UCB Status Changes ;+ ; This entries are called in RSX-11M-Plus systems to process CON requests. ; No such activity is allowed for the QC: device. ;- QCKRB: ;Ref. label QCUCB: ;Ref. label MOVB #IE.ABO,$SCERR ;Set error status RETURN ;Return to caller .ENDC ;R$$MPL .SBTTL QCPOOL * Internal Pool QCPOOL: ;Ref label .BLKB 10 ;Minimum pool .END