        .TITLE  ZFMOUNT - Mount Image for ZFACP
        .IDENT  /X01-000/

;++
; ZFMOUNT - Mount Image for ZFACP
;
; ABSTRACT:
;
;       This program mounts the ZFACP on the device ZFA0.  
;       The user must have at least MOUNT, CMKRNL, DETACH, and SETPRV
;       privileges.
;--

        .PAGE
        .SBTTL  External and local symbol definitions

        .LIBRARY \SYS$LIBRARY:LIB\

        $AQBDEF                                 ; ACP queue block
        $CCBDEF                                 ; Channel control block
        $DEVDEF                                 ; Device characteristics
        $DYNDEF                                 ; Data structure ID codes
        $IODEF                                  ; I/O function codes
        $PQLDEF                                 ; Process quota list
        $PRDEF                                  ; Processor register names
        $PRCDEF                                 ; Process mode bits
        $PSLDEF                                 ; Processor status longword
        $SSDEF                                  ; System status codes
        $UCBDEF                                 ; Unit control block
        $VCBDEF                                 ; Volume control block

ACP_K_WAITTIM = 60                      ; half-second tics to wait for ACP to
                                        ;  clear CREATING bit after awaken
EF_K_TIMER = 1                          ; event flag to use for $SETIMRs
EF_K_MNTQIO = 2                         ;  ... for mount $QIO
EF_M_TMRORMNT = <1@EF_K_TIMER> ! <1@EF_K_MNTQIO>

ZF_K_ACPTYPE = 250
ZF_K_ACPCLASS = 251

;       redefine some VCB fields

VCB_Q_CURIRP = VCB$L_CUR_FID
VCB_Q_IOSB = VCB$L_IXHDR2LBN

        .PAGE
        .SBTTL  Local storage

        .PSECT  READONLY_DATA   NOEXE, PIC, LONG, SHR, NOWRT

UIC:    .WORD   ^O01                            ; ACP UIC Group=001
        .WORD   ^O01                            ; ACP UIC = [001,001]

THIRTY_SEC:                                     ; 30-sec delta time for
        .LONG   -10*1000*1000*30,-1             ;  MOUNTING check

HALF_SEC:                                       ; half-sec delta time for
        .LONG   -10*1000*500,-1                 ;  CREATING check

PLIST:  .LONG   -1,-1                           ; give ACP all privileges

QLIST:  .BYTE   PQL$_ASTLM                      ; quota list for ACP process
        .LONG   40
        .BYTE   PQL$_BIOLM
        .LONG   20
        .BYTE   PQL$_BYTLM
        .LONG   16384
        .BYTE   PQL$_CPULM
        .LONG   0
        .BYTE   PQL$_DIOLM
        .LONG   20
        .BYTE   PQL$_ENQLM
        .LONG   128
        .BYTE   PQL$_FILLM
        .LONG   40
        .BYTE   PQL$_PGFLQUOTA
        .LONG   8000
        .BYTE   PQL$_PRCLM
        .LONG   4
        .BYTE   PQL$_TQELM
        .LONG   20
        .BYTE   PQL$_WSDEFAULT
        .LONG   512
        .BYTE   PQL$_WSQUOTA
        .LONG   512
        .BYTE   PQL$_WSEXTENT
        .LONG   2000
        .BYTE   PQL$_LISTEND

        .PSECT  LOCAL_DATA      NOEXE, PIC, LONG

SAVED_ERR_CODE:
        .LONG   0                               ; place to store error code

UNDO_FLAGS:
        .LONG   0                               ; bit mask showing what to undo

        _VIELD  UNDO,0,<-                       ;  in case of failure
                <DASSGN,,M>,-                   ;  dassgn the chl to the dvc
                <DEALACP,,M>,-                  ;  delete the ACP process
                <DEALAQB,,M>,-                  ;  delete the AQB
                <DEALVCB,,M>,-                  ;  delete the VCB
                <STOPACP,,M>,-                  ;  delete the ACP process
                <CNCLMNT,,M>>                   ;  cancel the MOUNT $QIO

EPID:   .BLKL   1                               ; EPID returned by $CREPRC
IPID:   .BLKL   1                               ; IPID derived from EPID

UCB_ADDR:                                       ; address of the mounted dvc's 
        .BLKL   1                               ;  UCB, 
VCB_ADDR:
        .BLKL   1                               ;  VCB,
AQB_ADDR:
        .BLKL   1                               ;  and AQB

ZF_CHN:
        .BLKW   1                               ; channel to the device

IMAGE:  .ASCID  /ZFACP/                         ; file name with ACP image
PRCNAM: .ASCID  /ZFA0ACP/                       ; name for ACP
ZF_DVC: .ASCID  /ZFA0/                          ; ZF device name

        .PAGE
        .SBTTL  MNT_START, Main Program
        .PSECT  CODE            SHR, NOWRT, PIC, LONG

        .ENTRY  MNT_START, ^M<>

        $CMKRNL_S       B^BEGIN                 ; change mode to kernel
        RET                                     ; all done

        .ENTRY  BEGIN, ^M<R2,R3,R4,R5>

;       assign a channel to the device; if successful, 
;       get its UCB address

        $ASSIGN_S  DEVNAM=ZF_DVC, CHAN=ZF_CHN,- ; assign channel to the device
                ACMODE=#PSL$C_USER              ;  at USER level
        BLBS    R0, 10$                         ; check for error
        RET                                     ;  return if n.g.
10$:    BISL2   #UNDO_M_DASSGN, UNDO_FLAGS      ; note cleanup task

        MOVZWL  ZF_CHN, R0                      ; obtain channel number
        JSB     G^IOC$VERIFYCHAN                ; get CCB addr in R1

                ; (This routine destroys R0-R3; also, it checks channel 
                ; accessibility for the previous mode--in this case, USER)

        BLBC_W  R0, ERR_EXIT                    ; check for error       

        MOVL    CCB$L_UCB(R1), R5               ; get ZF device UCB address
        MOVL    R5, UCB_ADDR                    ;  and save it

        MOVL    #SS$_DEVMOUNT, R0               ; assume device already mtd
        BBSSI_W #UCB$V_MOUNTING,UCB$L_STS(R5),- ; note device mounting
                ERR_EXIT                        ;  br if already mounting
        BITL    #DEV$M_MNT!DEV$M_DMT,-          ; is unit already mounted 
                UCB$L_DEVCHAR(R5)               ;  or marked for dismount?
        BNEQ_W  ERR_EXIT                        ;  br if yes to either

;       allocate the Volume Control Block (VCB)

        MOVZBL  #VCB$C_LENGTH, R1               ; set desired size 
        JSB     G^EXE$ALONONPAGED               ; attempt alloc, addr in R2
        BLBC_W  R0, ERR_EXIT                    ;  br if failed
        MOVL    R2, R4                          ; use R4 for the VCB
        MOVL    R4, VCB_ADDR                    ; save VCB address
        BISL2   #UNDO_M_DEALVCB, UNDO_FLAGS     ; note cleanup task

;       Initialize VCB 

        MOVB    #DYN$C_VCB, VCB$B_TYPE(R4)      ; record type
        MOVZBW  #VCB$C_LENGTH, VCB$W_SIZE(R4)   ;  and size
        MOVW    #1, VCB$W_TRANS(R4)             ; initialize trans. count
        MOVL    R4, UCB$L_VCB(R5)               ; store VCB address in the UCB
        CLRQ    VCB_Q_CURIRP(R4)                ; init the "current IRP" qhdr

;       look for an existing AQB with our class and acptype fields

        CALLS   #0, LOCK_IODB                   ; lock the I/O database

        MOVAL   G^IOC$GL_AQBLIST, R2            ; get addr of AQB pointer
AQB_SRCH:
        MOVL    (R2), R2                        ; get addr of next AQB
        BEQL    AQB_NOT_FOUND                   ; if zero pointer, end of list
        CMPB    #ZF_K_ACPTYPE,AQB$B_ACPTYPE(R2) ; check acptype
        BNEQ    AQB_SRCH                        ;  if no match, go to next AQB
        CMPB    #ZF_K_ACPCLASS, AQB$B_CLASS(R2) ; check acp class
        BNEQ    AQB_SRCH                        ;  if no match, go to next AQB
        BBS     #AQB$V_UNIQUE,AQB$B_STATUS(R2),-; if unique ACP,
                AQB_SRCH                        ;  go to next AQB

        ; here we have an existing AQB.  Hook the new VCB to it and go do
        ; the mount $QIO

        MOVL    R2, AQB_ADDR                    ; save the AQB address
        INCB    AQB$B_MNTCNT(R2)                ; bump the mount count
        MOVL    R2, VCB$L_AQB(R4)               ; store AQB address in VCB

        CALLS   #0, UNLOCK_IODB                 ; release the I/O database
        BRW     DO_MOUNT_QIO

AQB_NOT_FOUND:
        CALLS   #0, UNLOCK_IODB                 ; release the I/O database

;       Allocate a new ACP Queue Block (AQB)

        MOVZBL  #AQB$C_LENGTH, R1               ; set length
        JSB     G^EXE$ALONONPAGED               ; allocate AQB, addr in R2
        BLBC_W  R0, ERR_EXIT                    ;  br if n.g.
        BISL2   #UNDO_M_DEALAQB, UNDO_FLAGS     ; note cleanup task
        MOVL    R2, AQB_ADDR                    ; save the address

;       Initialize the AQB 

        MOVZBW  #AQB$C_LENGTH, AQB$W_SIZE(R2)   ; record size                   
        MOVB    #DYN$C_AQB, AQB$B_TYPE(R2)      ;  and type 
        CLRQ    AQB$L_ACPQFL(R2)                ; init the IRP queue
        MOVB    #1, AQB$B_MNTCNT(R2)            ;  and the mount count
        MOVB    #ZF_K_ACPTYPE,-                 ; note that
                AQB$B_ACPTYPE(R2)               ;  it's ours
        MOVB    #ZF_K_ACPCLASS,-                ; ditto
                AQB$B_CLASS(R2)                 ;  
        MOVB    #AQB$M_CREATING,-               ; start synchronization
                AQB$B_STATUS(R2)                ;  with ACP

        MOVL    VCB_ADDR, R4                    ; recover VCB address
        MOVL    R2, VCB$L_AQB(R4)               ; put AQB address in VCB

;       Start the ACP process 

        $CREPRC_S       PIDADR=EPID,-           ; PID returned
                        IMAGE=IMAGE,-           ; ACP image file
                        PRVADR=PLIST,-          ; privilege mask
                        QUOTA=QLIST,-           ; give large quotas
                        PRCNAM=PRCNAM,-         ; process name
                        BASPRI=#4,-             ; as with most ACPs
                        UIC=UIC,-               ; want detached process
                        STSFLG=#PRC$M_HIBER     ; start in HIB. state
        BLBC_W  R0, ERR_EXIT                    ;  br if failure
        BISL2   #UNDO_M_STOPACP, UNDO_FLAGS     ; note cleanup task

;       we create the ACP in the hibernate state because as soon as it
;       runs the ACP will look for an AQB containing its own (internal)
;       PID... and we haven't put it there yet; nor have we put the AQB
;       in the AQB list, where the ACP can find it.  We'll awaken the ACP
;       in a moment.  

        MOVL    EPID, R0                        ; get the EPID
        JSB     G^EXE$EPID_TO_IPID              ; convert to IPID
        MOVL    R0, IPID                        ; save the result
        MOVL    R0, AQB$L_ACPPID(R2)            ;  and put it in the AQB

;       Link AQB into AQB list 

        CALLS   #0, LOCK_IODB                   ; lock the I/O database

        MOVAB   G^IOC$GL_AQBLIST, R1            ; get AQB listhead
        MOVL    (R1),AQB$L_LINK(R2)             ; forward link
        MOVL    R2,(R1)                         ; put AQB at head

        CALLS   #0, UNLOCK_IODB                 ; release the database

;       now we can let the ACP run.  Awaken it so it can find its AQB,
;       clear the CREATING bit therein, and generally get itself ready
;       to do enter its main loop.  Meanwhile we sit in a timeout loop, 
;       waiting for the CREATING bit to go away.  If we time out, the
;       ACP has died or is otherwise in trouble, so we'll have to clean up.  

        $WAKE_S PIDADR=EPID                     ; kick it off
        MOVL    #ACP_K_WAITTIM, R3              ; init wait loop counter

WAIT_FOR_ACP:
        $SETIMR_S EFN=#EF_K_TIMER,-             ; start a timer
                DAYTIM=HALF_SEC                 ;  for half a second
        $WAITFR_S EFN=#EF_K_TIMER               ; wait for done
        BBC     #AQB$V_CREATING,-               ; if the bit is clear,
                AQB$B_STATUS(R2), 10$           ;  all is well
        SOBGTR  R3, WAIT_FOR_ACP                ; else try again

        ; if we get here, the ACP has taken more than the allotted time
        ; to clear the CREATING bit.  Clearly it's in trouble, so 
        ; get rid of it and exit.  

        BRW     ERR_EXIT

10$:
        ; if we get here, all is well; the ACP should be hibernating,
        ; waiting for requests from the driver.  

DO_MOUNT_QIO:

;       issue the $MOUNT $QIO and wait for the MOUNTING bit to clear

        $QIO_S  EFN=#EF_K_MNTQIO, CHAN=ZF_CHN,- ; start the mount
                FUNC=#IO$_MOUNT                 ;  $QIO
        BLBC_W  R0, ERR_EXIT                    ; br if error
        BISL2   #UNDO_M_CNCLMNT, UNDO_FLAGS     ; note cleanup task

;       wait up to thirty seconds for the mount $QIO to complete

        $SETIMR_S       EFN=#EF_K_TIMER,-       ; start a timer
                        DAYTIM=THIRTY_SEC       ;  request
        $WFLOR_S        EFN=#EF_K_TIMER,-       ; wait for 
                        MASK=#EF_M_TMRORMNT     ;  either event flag

;       Timeout or mount $QIO finished; check synchronization

        MOVL    UCB_ADDR, R5                    ; recover UCB address
        BBS_W   #UCB$V_MOUNTING,UCB$W_STS(R5),- ; error if MOUNTING
                ERR_EXIT                        ;  still set
        BBC_W   #DEV$V_MNT,UCB$L_DEVCHAR(R5),-  ; error if MNT 
                ERR_EXIT                        ;  not set

        $DASSGN_S CHAN=ZF_CHN                   ; deassign channel to pdvc

        MOVZWL  #SS$_NORMAL,R0                  ; return success code
        RET                                     ;  to caller

; ERR_EXIT -- come here if any of the above code detects a mistake.  
;       Look at the UNDO_FLAGS to see what we have to undo, if anything,
;       then return to caller.

ERR_EXIT:
        MOVL    R0, SAVED_ERR_CODE              ; remember why we're here

        BBCC    #UNDO_V_CNCLMNT,UNDO_FLAGS,10$  ; need to cancel MOUNT $QIO?
        $CANCEL_S CHAN=ZF_CHN
10$:

        BBCC    #UNDO_V_STOPACP,UNDO_FLAGS,20$  ; need to stop ACP?
        $DELPRC_S PIDADR=EPID                   ;  yes
        CALLS   #0, LOCK_IODB
        PUSHL   AQB_ADDR                        ; we also need to remove our
        CALLS   #1, UNLINK_AQB                  ;  AQB from the system's list
        CALLS   #0, UNLOCK_IODB
20$:

        BBCC    #UNDO_V_DEALAQB,UNDO_FLAGS,30$  ; need to delete AQB?
        MOVL    VCB_ADDR, R4                    ; yes.  Ensure that VCB
        CLRL    VCB$L_AQB(R4)                   ;  no longer points to it
        MOVL    AQB_ADDR, R0                    ; delete
        JSB     G^EXE$DEANONPAGED               ;  it
30$:

        BBCC    #UNDO_V_DEALVCB,UNDO_FLAGS,40$  ; need to delete VCB?
        MOVL    UCB_ADDR, R5                    ; yes.  Get UCB addr
        BICL    #UCB$M_MOUNTING,UCB$W_STS(R5)   ; note no longer mounting
        CLRL    UCB$L_VCB(R5)                   ; no longer have VCB
        MOVL    VCB_ADDR, R0                    ; delete
        JSB     G^EXE$DEANONPAGED               ;  the VCB
40$:

        BBCC    #UNDO_V_DASSGN,UNDO_FLAGS,50$   ; need to deassign channel?
        $DASSGN_S CHAN=ZF_CHN                   ;  yes
50$:
        MOVL    SAVED_ERR_CODE, R0              ; remember why we're here
        RET                                     ;  and return to caller

        .END    MNT_START

