PGM PDRIV
        REL
**********************************************************
*                                                        *
* Device driver for single character output devices ie.  *
* Console printer, paper tape punch etc.                 *
*                                                        *
**********************************************************
*
* States and interrupt vectors
*
MPICMSK EQU  #C2
COPRDY  EQU  1
ERRVEC  EQU  #3FC                 * ERROR TRAP VECTOR
*
* Device control block symbols
*
*                 0               ; DEVICE DRIVER PTR (BCPL)
*                 0               ; CODE LINK (=0)
*                 0
D.ID    EQU       6               * id
D.WKQ   EQU       8               * work queue
D.START EQU      10               * start routine - for QPKT
D.STOP  EQU      14               * stop routine - for DQPKT
D.CALL  EQU      18               * subroutine jump to
D.INT   EQU      20               * interrupt routine offset address
D.I     EQU D.CALL+6              * offset for interrupt rtn
D.VEC   EQU      24               * interrupt vector number
D.CSW   EQU      26               * control and status word
D.IOW   EQU      28            i/o address
*
* Packet symbols
*
P.ID    EQU       2               * task or device id
P.TYPE  EQU       4               * type or action
P.RES1  EQU       6               * first result
P.RES2  EQU       8               * second result
P.A1    EQU      10               * argument 1
*
* The rootnode
*
CRNTSK  EQU  #506
DEVMVP  EQU  #51A * MOVPKT for device drivers (MC addr)
DEVINT  EQU  #51E * INTENT for device drivers (MC addr)
DEVRET  EQU  #522 * INTRET for device drivers (MC addr)
        DW   PINITI          * initialisation rtn
        DW   PUNINI          * uninitialisation rtn
* Device initialisation routine. It is entered with the
* address of the DCB in BX. BX and DI must be preserved.
* assume console USART has been set up by the monitor
PINIT   MOV D.START(BX),!PSTART
        MOV D.STOP(BX),!PSTOP
        MOV D.START+2(BX),CS
        MOV D.STOP+2(BX),CS
        PUSH SI
        MOV SI,!PINT           make offset  for CALL in DCB
        MOV D.INT(BX),SI
        MOV D.INT+2(BX),CS     correct code seg in DCB
        MOV SI,D.VEC(BX)       get interrupt vector number
        ADD SI,!40
        SHL SI
        SHL SI
        MOV (SI),BX            plug interrupt vector with
        ADD (SI),!D.CALL       address
        MOV 2(SI),!0           CS := 0 for interrupt
        POP SI
        RETS
* Device uninitialisation routine. It is entered with
* the address of the DCB in BX, which must be preserved.
PUNIN   PUSH AX
        MOV AX,ERRVEC
        PUSH BX
        MOV BX,D.VEC(BX)       get address to plug
        ADD BX,!40
        SHL BX
        SHL BX
        MOV (BX),AX            plug it
        MOV AX,ERRVEC+2
        MOV 2(BX),AX
        POP BX
        POP AX
        RETS
* Device start routine. This is entered whenever a pkt
* is sent to the device and its work queue is empty. It
* is entered with the address of the DCB in BX, and the
* BCPL address of the packet in SI. It returns non-zero.
PSTART  MOV DX,D.CSW(BX)       get address of status register
        IN
        TEST AL,!COPRDY        device ready?
        JNE PST                yes - write immediately
PSTARTI MOV DL,!1              no - enable interrupts
        MOV CX,D.VEC(BX)       interrupt vector number
        SHL DL,CL
        NOT DL
        IN MPICMSK
        AND AL,DL
        OUT MPICMSK
        RETS
* Device stop routine. This is entered if the head pkt
* is dEQUeued from the device. It is entered with the
* DCB in BX, which must
* be preserved.
PSTOP   PUSH CX
        MOV DL,!1
        MOV CX,D.VEC(BX)       get interrupt number
        SHL DL,CL
        IN MPICMSK
        OR AL,DL
        OUT MPICMSK
        POP CX
        RETS
* This routine writes the head pkt and returns it
PST     SHL SI
*   check for line-feed, if found send 3 idles
        CMP P.A1(SI),!#A       line feed?
        JNE PSTA1
        MOV P.A1(SI),!0        set up idling
        MOV P.RES2(SI),!3      idle count
        MOV AL,!#A
        MOV DX,D.IOW(BX)
        OUT
        PUSH CS
        CALL PSTARTI           enable interrupts
        B PSTA2
*
PSTA1   CMP P.A1(SI),!0        idling?
        JNE PSTA3              no
        DEC P.RES2(SI)
        JE PSTA3               handle last idle normally
        CMP P.RES2(SI),!3      see if count in range
        JAE PSTA3              not one of mine
        PUSH CS
        CALL PSTARTI           enable interrupts with impunity
        MOV DX,D.IOW(BX)
        XOR AL,AL
        OUT
PSTA2   MOV AX,CRNTSK
        SHL AX
        RETS
*
PSTA3   MOV DX,(SI)            dequeue pkt
        MOV D.WKQ(BX),DX
        OR DX,DX
        JNE PST1
        MOV CX,D.VEC(BX)        disable interrupts
        MOV DL,!1
        SHL DL,CL
        IN MPICMSK
        OR AL,DL
        OUT MPICMSK
PST1    MOV CX,D.ID(BX)
        MOV DX,D.IOW(BX)     write char after disabling
        MOV AX,P.A1(SI)      get it
        OUT                  write it
        MOV BX,CRNTSK
        SHL BX                  getting args ready for MOVPKT
        JIS DEVMVP               MOVPKT will execute RETS instruction
* This code is entered by the subroutine jump in the DCB
* which in turn was entered via the interrupt vector. The last
* word pushed onto the m/c stack therefore is the address of the
* DCB + D.I
PINT    PUSH BX
        PUSH AX
        PUSH CX
        PUSH DX
        PUSH SI
        MOV BX,SP              get stack pointer
        MOV BX,10(BX)          get IP (points into DCB)
        SUB BX,!D.I
        MOV SI,D.WKQ(BX)       get BCPL pkt ptr
        OR SI,SI
        JE PINTOUT         MAY GET SPUROUS INT. AFTER STOPPING
        PUSH CS
        CALL PST
        JIS DEVINT              return via INTENT
PINTOUT JIS DEVRET
*
        EVEN
        DSEG
        EVEN
PINITI  DW PINIT
        DW 0
PUNINI  DW PUNIN
        DW 0
        END