.TITLE ZFACP - Example "File System" ACP .SBTTL External and local symbol definitions .IDENT \X01-000\ ;++ ; ZFACP - Example "File System" ACP ; ; Register conventions used throughout this image: ; ; R0 VCB address ; R3 IRP address ; R4 PCB address ; R5 UCB address ; R6 AQB address ;-- ; standard VMS data structures .LIBRARY \SYS$LIBRARY:LIB\ $ACBDEF ; AST control block $AQBDEF ; ACP queue block $CCBDEF ; Channel control block $DCDEF ; Device classes and types $DDBDEF ; Device data block $DEVDEF ; Device characteristics $DYNDEF ; Data structure ID codes $IODEF ; I/O function codes $IPLDEF ; Interrupt priority levels $IRPDEF ; I/O request packet $IRPEDEF ; IRP Extension $PCBDEF ; Process control block $PSLDEF ; Processor status longword $SSDEF ; System status codes $UCBDEF ; Unit control block $VCBDEF ; Volume control block ; bits in UCB$L_DEVDEPEND $DEFINI UCB _VIELD ZF,0,<> ; file is open $DEFEND UCB ; redefine some VCB fields VCB_Q_CURIRP = VCB$L_CUR_FID VCB_Q_IOSB = VCB$L_IXHDR2LBN ZF_K_ACPTYPE = 250 ZF_K_ACPCLASS = 251 .PAGE .SBTTL Local storage .PSECT LOCAL_DATA NOEXE, PIC, LONG .ALIGN LONG CODE_LOCK_RANGE: .ADDRESS DISMOUNT_CHECK ; argument for $LKWSET .ADDRESS DISMOUNT_CHECK_END NLA0_CCB_ADDR: .BLKL 1 AQB_ADDR: .BLKL 1 ; addr of our AQB NLA0_CHAN: .BLKW 1 ; channel to null device NLA0_DVCNAM: .ASCID /_NLA0/ ; null device name CLOSE_FILE_MSG: ; message to write when .ASCII /FILE CLOSED/ ; "closing" a "file" CFM_K_LENGTH = . - CLOSE_FILE_MSG ; length thereof .PSECT CODE SHR, NOWRT, PIC, LONG .PAGE .SBTTL ACP_MAIN, Main Program .ENTRY ACP_MAIN, ^M<> $CMKRNL_S B^BEGIN ; change mode to kernel RET ; all done .ENTRY BEGIN, ^M ; when we first start, we have been invoked via the $WAKE call in ; the ACP mount image. We're expected to clear the CREATING bit ; in the AQB, remember where the AQB is for future reference, and ; then enter the grand loop, where we hibernate until the mount ; image does the IO$_MOUNT $QIO. MOVL G^CTL$GL_PCB, R4 ; get our PCB address MOVL PCB$L_PID(R4), R4 ; and from there, our IPID CALLS #0, LOCKR_IODB ; lock I/O data base (IPL 2) MOVAL G^IOC$GL_AQBLIST, R1 ; get AQB listhead addr in R1 MOVL (R1), R6 ; get address of first AQB 10$: CMPL R4, AQB$L_ACPPID(R6) ; found right AQB? BEQL FOUND_AQB ; if eql, yes MOVL AQB$L_LINK(R6), R6 ; get next link BNEQ 10$ ; and loop CALLS #0, UNLOCK_IODB ; else release mutex, IPL 0 MOVL #SS$_NOAQB, R0 ; AQB not found RET FOUND_AQB: CALLS #0, UNLOCK_IODB ; release mutex, lower IPL MOVL R6, AQB_ADDR ; save AQB address BBSC #AQB$V_CREATING,- ; check the creating bit, tell AQB$B_STATUS(R6), 10$ ; mount image we're running MOVZWL #SS$_WRONGACP, R0 ; if not set, return err status RET ; if we're here, we're in good shape. 10$: $LKWSET_S INADR=CODE_LOCK_RANGE ; lock code that is to run at ; elevated IPL ; assign a channel to the null device so that we have a CCB for $QIO calls $ASSIGN_S DEVNAM=NLA0_DVCNAM,- CHAN=NLA0_CHAN,- ; assign channel to NLA0 ACMODE=#PSL$C_USER ; at USER level BLBS R0, 20$ ; check for error RET ; return if n.g. 20$: MOVZWL NLA0_CHAN, R0 ; obtain channel number JSB G^IOC$VERIFYCHAN ; get CCB addr in R1 BLBS R0, 30$ ; check for error RET 30$: MOVL R1, NLA0_CCB_ADDR ; save the CCB address BRB ACP_LOOP ; go look for work .PAGE .SBTTL ACP_LOOP, ACP Main Processing Loop ACP_LOOP: ; look for and process all IRPs in the AQB queue MOVL AQB_ADDR, R6 ; get addr of ACP Queue Block 10$: REMQHI AQB$L_ACPQFL(R6), R3 ; try to get an IRP BVS 20$ ; br if queue empty CMPB IRP$B_TYPE(R3), #DYN$C_IRP ; is it really an IRP? BEQL 15$ ; br if yes PUSHL R3 ; else assume it's a VCB ($QIO CALLS #1, PROCESS_VCB ; completion notification) BRB 10$ ; look for next IRP or VCB 15$: PUSHL R3 ; else pass it to the CALLS #1, PROCESS_IRP ; IRP processor BRB 10$ ; and look for another 20$: ; come here when queue empty CALLS #0, CHECK_ACP_DONE ; delete ACP if necessary $HIBER_S ; else hibernate; upon BRW 10$ ; awakening, look for more work .PAGE .SBTTL PROCESS_IRP, Process an IRP ;++ ; Inputs: ; 4(AP) Address of the IRP ;-- .ENTRY PROCESS_IRP, ^M MOVL 4(AP), R3 ; get the IRP address MOVL IRP$L_UCB(R3), R5 ; get the UCB address MOVL AQB_ADDR, R6 ; get the AQB address EXTZV S^#IO$V_FCODE, S^#IO$S_FCODE,- ; get function code from IRP, IRP$W_FUNC(R3), R0 ; ignoring modifier bits ; branch according to I/O function code CMPL R0, #IO$_MOUNT ; mount volume request BEQL DO_MOUNT CMPL R0, #IO$_ACPCONTROL ; dismount volume request BEQL DO_DISMOUNT CMPL R0, #IO$_CREATE ; "open file" request BEQL DO_OPENFILE CMPL R0, #IO$_DEACCESS ; "close file" request BEQL DO_CLOSEFILE MOVL #SS$_ILLIOFUNC, IRP$L_IOST1(R3) ; no other legal funcs PUSHL R3 ; for this ACP CALLS #1, POST_BAD_IRP ; so complain RET .PAGE ; handle volume mount requests DO_MOUNT: CMPB #ZF_K_ACPTYPE,- ; check ACP type against AQB$B_ACPTYPE(R6) ; our type code BEQL 10$ ; br if ok MOVL #SS$_WRONGACP, IRP$L_IOST1(R3) ; else PUSHL R3 ; post IRP with error CALLS #1, POST_BAD_IRP ; status RET ; and return 10$: BISL2 #DEV$M_MNT, UCB$L_DEVCHAR(R5) ; set MOUNTED bit PUSHL R3 ; send IRP CALLS #1, POST_GOOD_IRP ; to posting RET ; and return ; handle volume dismount requests; not much to do here! DO_DISMOUNT: PUSHL R3 ; send IRP CALLS #1, POST_GOOD_IRP ; to posting RET ; and return ; handle "file open" requests (just like writes, but they happen ; to come through the ACP) DO_OPENFILE: JSB G^EXE$INSIOQ ; send user's IRP to driver MOVL UCB$L_VCB(R5), R0 ; get VCB address ADAWI #-1, VCB$W_TRANS(R0) ; note one fewer pending op PUSHL R5 ; dismount the volume CALLS #1, DISMOUNT_CHECK ; if time to do so RET .PAGE ; handle "file close" requests ; (issue our own $QIO to the device) DO_CLOSEFILE: MOVL UCB$L_VCB(R3), R0 ; get the VCB address INSQTI (R3), VCB_Q_CURIRP(R0) ; stash the user's IRP MOVL NLA0_CCB_ADDR, R1 ; get the CCB address MOVL R5, CCB$L_UCB(R1) ; point CCB to the right dvc $QIO_S CHAN=NLA0_CHAN,- ; issue $QIO on the channel FUNC=#IO$_WRITEVBLK,- ; ordinary write function IOSB=VCB_Q_IOSB(R0),- ; IOSB is written to the VCB ASTADR=IO_COMPL_AST,- ; AST procedure entry point ASTPRM=R0,- ; AST is notified of VCB addr P1=CLOSE_FILE_MSG,- ; write a simple P2=#CFM_K_LENGTH ; message RET ; return to main loop ; when the above $QIO completes, the system will call this AST ; procedure, passing it the address of the VCB. .ENTRY IO_COMPL_AST, ^M MOVL 4(AP), R0 ; get VCB address (AST param) MOVL VCB$L_AQB(R0), R6 ; get the AQB address INSQTI (R0), AQB$L_ACPQFL(R6) ; put the VCB on the ACP's ; work queue $WAKE_S ; wake ourselves MOVZWL #SS$_NORMAL, R0 ; and RET ; return ; when the ACP main loop finds the VCB on its work queue, ; it will call this procedure, passing it the address of the VCB. .ENTRY PROCESS_VCB, ^M MOVL 4(AP), R0 ; get VCB address REMQHI VCB_Q_CURIRP(R0), R3 ; get user's IRP MOVQ VCB_Q_IOSB(R0), IRP$L_IOST1(R3) ; set up IOSB BLBC IRP$L_IOST1(R3), 10$ ; br if completed unsuccessfully MOVL IRP$L_UCB(R3), R5 ; else get the UCB address BICL2 #ZF_M_FOPEN, UCB$L_DEVDEPEND(R5); and clear the file open bit 10$: PUSHL R3 ; send the user's IRP CALLS #1, POST_IRP ; to posting MOVZWL #SS$_NORMAL, R0 ; and RET ; return .PAGE .SBTTL POST_xxx_IRP, IRP posting routines ;++ ; POST_IRP, post IRP with caller-specified status ; POST_GOOD_IRP, post IRP with caller-specified status ; POST_BAD_IRP, post IRP with caller-specified status ; ; Abstract: ; ; POST_IRP is a common routine which posts completion for an IRP. ; POST_GOOD_IRP writes SS$_NORMAL into the IOSB field in the IRP, ; then falls through to POST_IRP. ; POST_BAD_IRP clears the second longword of the IOSB in the IRP, ; then falls through to POST_IRP. (The caller should fill in ; the first longword.) ; ; All of the above entry points decrement the VCB transaction ; count and call DISMOUNT_CHECK, which will dismount the volume ; (deallocate the VCB, etc.) if the transaction count is zero ; and the DMT bit is set. ; ; Inputs: ; 4(AP) - IRP address ;-- .ENTRY POST_GOOD_IRP, ^M MOVL 4(AP), R3 ; get IRP address MOVZWL #SS$_NORMAL, R0 ; set good status CLRL IRP$L_IOST2(R3) ; clear second lw of IOSB BRB POST_IRP_COMMON .ENTRY POST_BAD_IRP, ^M MOVL 4(AP), R3 ; get IRP address CLRL IRP$L_IOST2(R3) ; clear second lw of IOSB BRB POST_IRP_COMMON .ENTRY POST_IRP, ^M MOVL 4(AP), R3 ; get IRP address POST_IRP_COMMON: MOVL IRP$L_UCB(R3), R5 ; get UCB address JSB G^COM$POST ; send IRP to posting MOVL UCB$L_VCB(R5), R0 ; get VCB address ADAWI #-1, VCB$W_TRANS(R0) ; note one I/O done PUSHL R5 ; see if volume can be CALLS #1, DISMOUNT_CHECK ; dismounted RET ; and return to caller .PAGE .SBTTL DISMOUNT_CHECK, Check for volume dismount ;++ ; DISMOUNT_CHECK, Check for volume dismount ; ; Abstract: ; ; Check to see if UCB is marked for dismount; ; if so, and if volume is idle, dismount the ACP from the UCB. ; We call this procedure whenever the ACP gets rid of an IRP, ; either by sending it to posting or by requeuing it to the ; driver (ie whenever we decrement VCB$W_TRANS). ; ; Inputs: ; ; 4(AP) - UCB address ;-- .ENTRY DISMOUNT_CHECK, ^M MOVL 4(AP), R5 ; get UCB address BBC_W S^#DEV$V_DMT,- ; br if unit not UCB$L_DEVCHAR(R5), 100$ ; marked for dismount CALLS #0, LOCK_IODB ; lock the I/O database mutex FORKLOCK SAVIPL=-(SP) ; acquire the forklock MOVL UCB$L_VCB(R5), R0 ; get VCB address CMPW VCB$W_TRANS(R0), #1 ; is volume idle? BNEQ 20$ ; br if no ; note that 1, not 0, is the inited value of VCB$W_TRANS. BICL2 #DEV$M_MNT!DEV$M_DMT,- ; clear mounted and UCB$L_DEVCHAR(R5) ; dismounting bits CLRL UCB$L_VCB(R5) ; disconnect VCB from UCB MOVL VCB$L_AQB(R0), R6 ; get AQB address JSB G^COM$DRVDEALMEM ; deallocate VCB DECB AQB$B_MNTCNT(R6) ; note one less vol on ACP 20$: FORKUNLOCK NEWIPL=(SP)+ ; release the forklock CALLS #0, UNLOCK_IODB ; unlock I/O database mutex 100$: MOVZWL #SS$_NORMAL, R0 RET DISMOUNT_CHECK_END: ; label for $LKWSET .PAGE .SBTTL CHECK_ACP_DONE, Delete ACP if appropriate ;++ ; CHECK_ACP_DONE, Delete ACP if appropriate ; ; Abstract: ; ; This routine is called when the main loop finds no more work for the ; ACP. If there are no more volumes associated with the ACP, the ; routine deletes the ACP data structures and the ACP itself (and so ; never returns). ; ; Implicit inputs: ; ; AQB_ADDR - address of AQB ;-- .ENTRY CHECK_ACP_DONE, ^M MOVL AQB_ADDR, R6 ; get AQB address TSTB AQB$B_MNTCNT(R6) ; check ACP's volume count BEQL 10$ ; br if no more volumes RET ; else return to caller 10$: CALLS #0, LOCK_IODB ; lock the I/O data base PUSHL R6 ; remove the AQB CALLS #1, UNLINK_AQB ; from the system's list MOVL R6, R0 ; set up for deallocate JSB G^EXE$DEANONPAGED ; and do it CALLS #0, UNLOCK_IODB ; unlock I/O data base $DELPRC_S ; and kill ourselves off .END ACP_MAIN